Commit Graph
24 Commits
Author SHA1 Message Date
kevinveenbirkenbach d0698daa5e Merge pull request #414 from pragepani/feature/web-app-wazuh/add-role
feat(web-app-wazuh): add Wazuh SIEM role with OIDC/LDAP RBAC
2026-10-19 14:35:24 +02:00
Prageeth Panicker 885de8e4a6 fix(web-app-wazuh): resolve platform-drift lint/integration failures from the 900-commit main sync
Seven real, branch-caused failures surfaced after rebasing onto main
(v14.0.0, ~900 commits):

- opensearch_dashboards_yml volume: type: config -> type: secret
  (renders a live OIDC client secret; was world-readable at 0444 in a
  volume type the platform treats as non-sensitive, same fix pattern
  as web-app-gitlab's rails_secrets_yml)
- meta/networks.yml: local.subnet collided with web-app-litellm
- meta/services.yml: indexer/console ports collided with
  svc-ai-litellm and web-app-openclaw (both added during the gap)
- compose.yml.j2: healthcheck interval/timeout/start_period now go
  through the SPOT lookup instead of hardcoded values
- README.md: regenerated Quick Setup section, fixed credits link
  formatting
- docs/requirements/035-mcp-proxy-expansion.md: added web-app-wazuh
  to the adapter_candidate list (has a real, currently-unpublished
  API surface - manager :55000, indexer :9200 - not yet evaluated
  for an MCP adapter, same bucket web-app-checkmk/prometheus sat in
  before promotion)

Two regressions caught by re-verification and fixed in the same pass:
meta/services.yml briefly reached into a role var directly (fixed to
use the lookup('config', ...) pattern already used elsewhere), and the
credits-link fix dropped required bold markdown.

Remaining test failures after this fix are confirmed pre-existing and
unrelated: svc-net-tor's nftables guard rule (kernel EOPNOTSUPP), an
INFINITO_SWARM_STEP_TIMEOUT_MINUTES Makefile/default.env drift, and a
CI chunk-budget drift.
2026-10-06 05:15:14 +00:00
evangelostsak 4e44d7fb84 Merge remote-tracking branch 'upstream/main' into feature/svc-net-wireguard
No conflicts. Two files were touched by both sides and both kept each
side: the Makefile, where upstream retires the mcp-audit target and this
branch pins the throwaway cluster's vpn axis, and requirement 035, where
the role table keeps the single svc-net-wireguard row.

Incoming carries the fixes for two applications this branch's last run
failed on -- penpot probing its frontend without curl, and mobilizon's
postgres 18 migrations -- plus dependency bumps and a lint repair.

Gates on the merged tree: unit 6202, lint 523, integration 330,
ansible-lint clean.
2026-10-05 12:17:12 +02:00
kevinveenbirkenbachandClaude Opus 5.5 5b002678df docs(mcp): remove the generated audit report
The report aggregated each role's meta/mcp.yml into one table, and its
freshness test failed on every image-version bump of an MCP-serving role,
because the updater edits services.yml and nothing regenerates the committed
report. The table added nothing the sources lack: scope and credential subject
were identical for every role, so it restated its inputs.

The generator, the report, its freshness test, make mcp-audit and the i8mcau
alias go, and make docs no longer calls the generator. The metadata stays in
each role's meta/mcp.yml, where test_mcp_schema.py enforces its shape, and
requirement 035 now says so. The classification and completeness lints stay,
because they check the lists in 035 against the roles, not the report.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 14:36:52 +02:00
evangelostsak e456b1eee6 Merge remote-tracking branch 'origin/main' into feature/svc-net-wireguard
One conflict, in the MCP exclusion list of requirement 035. Both sides
edited the same line: main added svc-net-tor-smtp, this branch replaced the
three svc-net-wireguard-{core,plain,firewalled} entries with the single
svc-net-wireguard it deploys. Resolved to the union of both — 24 entries —
which is what the merged tree actually holds.

Everything else merged clean and was checked for overlap rather than
assumed: no reference anywhere still names the three replaced roles;
matrix.py kept main's PRIMARY_APPS alongside the reordered round, and the
bootstrap pass does not need it, since only one template reads it and
group_vars defaults it empty; and main's ansible.cfg addition sits in
[defaults], so the ssh_args the mesh tool derives its own connection
settings from are untouched.
2026-10-03 02:29:45 +02:00
evangelostsak 0a6700e378 Merge origin/main into feature/svc-net-wireguard
Brings in 225 commits, including the merged web-app-openbao work and the
LMStudio loopback fix.

Seven conflicts:

- The legacy svc-net-wireguard-core/plain/firewalled roles this branch deleted
  in e706170a0 were modified upstream. The deletion stands, which is the whole
  premise of the branch.
- The MCP audit takes upstream's list plus svc-net-firewall, with the three
  legacy wireguard entries collapsed into the single svc-net-wireguard: 23.
- web-app-openbao/README.md takes main's reviewed version wholesale.
- matrix.py takes both sides: upstream's app_id argument to _reset_credentials
  and this branch's _write_mesh call after it.

The merge pushed matrix.py to 511 lines against the 500 cap, so the step
runner moved to utils/tests/swarm/run.py, which is self-contained and leaves
matrix.py at 427. Its test moved with it.
2026-09-24 21:14:25 +02:00
Alejandro Roman c8894b2dd2 Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-22 22:33:13 +04:00
Alejandro Roman 4b9a834546 Merge remote-tracking branch 'upstream/main' into feature/role-stalwart-mail-swarm 2026-09-21 03:25:20 +04:00
evangelostsak c762924675 Merge upstream/main into feature/web-app-openbao
Brings in Kevin's AI-agent work, the svc-net-firewall role and the update
tooling. One conflict, in the MCP proxy audit: both sides appended a role to
privileged_control_plane, ours web-app-openbao and upstream svc-net-firewall.
Taken as a union at 25, which is what test_mcp_audit_completeness requires -
neither role declares meta/mcp.yml, so the audit is the only place they are
classified.
2026-09-19 20:07:00 +02:00
evangelostsak ac0f08f181 docs(mcp): dispose of svc-net-wireguard as privileged control plane
The role alters host routing and forwarding, so it belongs with the roles that
require an audited operations gateway rather than general-purpose application
MCP. Absence is not a disposition, and the completeness lint enforces that.
2026-09-18 10:36:24 +02:00
evangelostsak d783d83560 Merge feature/web-app-openbao into feature/svc-net-wireguard
Brings this pre-swarm branch up to the current swarm-era codebase: 3681 commits
across 7221 files. Our side contributed almost nothing by comparison, so the
conflict surface stayed at the 17 files both sides had touched.

Resolutions:
- The 15 legacy svc-net-wireguard-{core,firewalled,plain} files conflicted as
  modify/delete and rename/delete (upstream renamed 01_core.yml to 00_core.yml).
  All resolved as deletions: this branch is starting wireguard over. Five more
  files of those roles came back silently because upstream had not touched them
  since the merge base, and were removed after the merge.
- sys-svc-container/templates/daemon.json.j2 took upstream's structure, which
  adds default-address-pools and json-file log limits and makes storage-driver
  conditional on DOCKER_IN_CONTAINER. Our unconditional version would have set
  fuse-overlayfs on bare-metal hosts too, so theirs is the correct semantics;
  only the value now routes through SYS_SVC_CONTAINER_STORAGE_DRIVER.
- sys-svc-container/vars/main.yml keeps that variable, which upstream never had
  and nothing supersedes, and adopts upstream's removal of the MTU comment.

Follow-up the merge itself required: the root README role table was regenerated
and the privileged_control_plane list in requirement 035 dropped from 24 to 21,
both of which still enumerated the deleted roles.
2026-09-18 00:41:03 +02:00
kevinveenbirkenbachandClaude Opus 5 bf32f57b85 feat(svc-net-firewall): own the packet filter as one nftables table per role
svc-net-tor rendered its own guard script, its own unit and its own handler, and hooked the rules into DOCKER-USER with an insert, because a rule appended to a chain docker owns lands behind the RETURN docker puts there. The script flushed its chain and refilled it rule by rule, so the boundary was gone for the duration of the refill.

A role that needs packet filter rules now writes templates/nftables.conf.j2 declaring its own table and includes svc-net-firewall, which renders it to /etc/nftables.d/<role>.conf, rejects it with nft -c, loads it, reads the table back and refuses one that carries no chain. Deployed on its own the role installs nftables and the ruleset that loads every fragment at boot. That ruleset is a distribution decision, so its path follows os_family: RedHat reads /etc/sysconfig/nftables.conf, the others /etc/nftables.conf. The unit is only enabled, never started, because on Debian its ExecStop flushes the whole ruleset, docker's tables included.

One table per role removes the shared path: a fragment replaces its own table and touches no other, and docker never touches either. nft -f on an existing table merges, so each fragment creates, deletes and redeclares its table in one transaction. The tests load every fragment twice into a throwaway network namespace and require the listing to match, because a merging fragment loads twice without error and shows up only as a duplicated rule.

The tor guard now applies in both deploy modes. It was pinned to swarm while torrc binds TransPort and DNSPort on the bridge address in compose too, so on a single-host install those ports answered every on-link neighbour. The guard drops rather than admits, so nothing short-circuits another table, and ip saddr carries an implicit ipv4 dependency, which leaves the ipv6 exposure exactly where the iptables rules left it. svc-net-tor reads its own chain back and refuses a guard that drops nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 20:02:36 +02:00
evangelostsak 912b6cda39 Merge infinito-nexus/core#454 (feature/ai-agent-employees) into feature/web-app-openbao
Only two files were touched by both sides. env.js merged automatically: the
incoming side owns recordVideoOptions and this branch had cherry-picked that
same function from it, so the result carries one definition. README.md is the
generated roles index and both sides append rows; resolved as a union, taking
the incoming side for the three rows it edits (OnlyOffice, Open WebUI, OpenClaw)
and keeping ours only for OpenBao, then re-sorted.

The incoming side adds lint rules and resources that this role then violated.
Five fixes, each the remedy the failing rule names:

* services.openbao.ports.local.http 8081 -> 8093; svc-ai-litellm now holds 8081.
* meta/networks.yml subnet 192.168.221.0/24 -> 192.168.222.0/24; svc-ai-mcp-adapter
  now holds 221, and the suggester only sub-allocates inside occupied /24s, so
  the next free block was taken directly.
* services.email bond 1 -> 0; the entry is disabled and the resource model skips
  it before reading the weight.
* README Quick Setup regenerated from the template it had drifted from.
* Credits link the implementer, matching how they are credited elsewhere.
* docs/requirements/035-mcp-proxy-expansion.md disposes of web-app-openbao as
  privileged_control_plane, which is what a secrets manager is, and the list
  count follows.

Verified against a worktree of #454 alone so the baseline is measured rather
than assumed: lint 501 passed with only the branch-name rule failing, which
fails identically on that baseline because the worktree branch is named
merge/*; unit 5665 passed with the same three host-environment failures the
baseline has; integration 324 passed, 0 failed.
2026-09-14 16:59:39 +02:00
kevinveenbirkenbachandClaude Opus 5 3db5820b6e fix(n8n): serve the MCP trigger over Streamable HTTP on 1.100.1 so Open WebUI can connect
Run 34421944328 n8n#1: Open WebUI 0.11.0 ships only a Streamable HTTP
MCP client. n8n 1.95.3's MCP Server Trigger speaks SSE only and answered
the client's POST to /webhook/infinito-mcp/sse with 404, so "every
registered MCP server answers Open WebUI's client" failed.

n8n moves to 1.100.1 and the managed trigger to typeVersion 2, which
serves Streamable HTTP at /mcp/<path>. meta/mcp.yml declares
transport: streamable_http at /mcp/infinito-mcp (the trigger path stays
infinito-mcp) and points source_url at the 1.100.1 tree. Open WebUI's
supported_transports drops sse, which its client never spoke; no
provider declares sse any more, so no pairing is lost.

n8n#0: the guest spec probed ${APP_BASE_URL}${MCP_ENDPOINT_PATH} and
expected a refusal of >= 300 without an event stream. The trigger is
exposure: internal, so the public vhost must answer 404; the spec now
asserts exactly that through mcpEndpointUrl(..., N8N_BASE_URL), and the
bearer guard itself is proven in-cluster by the CLI MCP contract.

Requirement 035 and the MCP audit table follow the version and
transport.

Upgrade path, read from upstream at both tags: the postgres migrations
are identical (45/45) and the only new common migration is
AddProjectDescriptionColumn, an additive nullable column; BREAKING-
CHANGES.md lists only 1.98.0 in range (route metric format, Form node
tag attributes, Node >= 20), none of which this deployment uses. A fresh
1.100.1 install ran all 89 migrations and passed the MCP bridge, server,
non-member and guest specs locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 12:20:46 +02:00
kevinveenbirkenbachandClaude Opus 5 3d6b3900e0 refactor(cli): gather the documentation generators under cli/build/docs
Two of them lived inside docs/ next to the pages they render, which is why both had grown a __file__ upward walk to find the repository root, a sys.path insert to import from utils, and a nocheck for each. A generator parked among documents reads as documentation and drops out of the checks that apply to code.

Moving them surfaced six real violations that the old location had hidden: yaml.safe_load instead of the shared cache, two uncached read_text calls, five hard-coded role paths instead of ROLE_FILE_META_SERVICES and ROLE_FILE_META_MCP, two make targets without an alias, and a requirement reference in code. All six are fixed at the root, none with a suppression, and both generators lose their project-root exemptions because a package supplies PROJECT_ROOT.

cli/build/readme joins them so the three generators that write into docs/ sit together. A new lint keeps docs/ to .md, .html, .rst and .nocheck; it enumerates through git ls-files so untracked __pycache__ is not its business. Checked against HEAD, it names exactly the three .py files that moved.

026-unified-addon-syntax stated that the matrix generator stays under docs/requirements/. The lint makes that impossible, so the requirement now records where the generator actually lives.

Verified: every one of the twelve modules under the new tree imports, no old module path survives anywhere in the repository, 482 lint tests and 1246 cli unit tests pass, and both generators run without drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 23:17:55 +02:00
kevinveenbirkenbachandClaude Opus 5 70feb319be feat(nextcloud): front the Context Agent with the MCP adapter
The hub serves 94 tools across 23 categories, write tools included, and enabling the ExApp switches every category on. That surface was published on the public vhost and every discovered client was handed an app password for it. svc-ai-mcp-adapter now sits in front in mcp_passthrough mode: clients present credentials.mcp_bearer to contextagentmcp and reach the nine read tools of calendar, contacts and files, which files/mcp/tools.json names and the sidecar enforces per call. Those three categories are the ones whose upstream is_available is unconditional, so the served set does not vary with which optional apps a deployment installs.

Enforcing it in the sidecar rather than in each client is what makes it hold: web-app-openwebui renders no per-tool filter at all, so a client-side include list would have bounded two consumers out of three.

06c13e7f3e withdrew the declared path from the edge for exactly this reason - "that surface is what the adapter exists to narrow". Nextcloud answers the same route under a second spelling, without the /index.php front controller, which try_files rewrites back onto the route after the edge has already declined to block it. The withdrawal becomes a regex covering both, and only where stripping the front controller changes the path, so the four adapter providers without one render unchanged.

The app password is minted after the stack renders, so it reaches the sidecar through upstream.env and rebuild.yml rather than the compose environment: compose up resolves env_file while creating the container, a restart does not. The sidecar is the last service in the template because a Jinja set has no block scope and its env_file would otherwise displace the one talk, whiteboard and talk_recording each carry.

Two suites cover what nothing measured: the vhost template rendered without a container, asserting both spellings are withdrawn, and every shipped tools.json loaded through the adapter own loader, so a bad contract fails in a second instead of as a probe timing out an hour into a deploy. The guest spec probed mcp.endpoint.path, which this change makes the proxy answer 404 for its own reasons; it now probes all three public paths and requires no MCP body.

Not yet deployed: the sidecar build, the upstream.env delivery and the hub answering exactly nine tools are unverified against a running stack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:55:05 +02:00
kevinveenbirkenbachandClaude Opus 5 346ad875c6 refactor(mcp): drop the two provenance fields nothing reads
supported_version and minimum_version sat in 24 roles and were read by no template, no task and no lookup. Their only consumer was the audit report version column, which turned false the moment an automated image bump ran: qdrant reported a verified version nobody had verified, two releases long. Both leave MCP_KEYS, so a role that declares them again fails the schema lint instead of carrying a field with no effect.

Requirement 025 mandated minimum_version as a MUST and 035 carried it in an example; both are corrected here, otherwise the repository would contradict its own code.

The gate then surfaced five dead docs.mattermost.com links. Mattermost moved to /administration-guide/ and /deployment-guide/, and every replacement was probed for 200 rather than guessed. The MCP page is gone with no successor, so mattermost now points at mcpserver/README.md in the Agents repository, the source rather than a rendered mirror.

Both concerns share three files - mattermost's meta/mcp.yml, requirement 025, and the generated audit.md, which derives from a source of each - so splitting them would commit an artifact that does not match its sources.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 19:31:08 +02:00
kevinveenbirkenbachandClaude Opus 5 791eee7c9f refactor(mcp)!: prove the endpoint contract with a CLI test, not a deploy probe
The contract check ran as an Ansible task in the middle of every provider deploy, and the two client checks ran the same way. A test that is part of the deploy cannot be run without deploying, cannot be re-run on its own, and reports its verdict as a deploy failure. Twenty-seven task files lose 491 lines to this.

It is now what it always was. Each provider ships files/test/test.sh, templates/test.env.j2 and meta/tests.yml, and test-e2e-cli stages one shared harness beside it: run.sh drives contract.sh in a throwaway container on the provider's own network, then isolation.sh for adapter-fronted providers. The read probe each provider offers moved from the calling task into meta/mcp.yml next to the allowlist it must satisfy, and the schema now rejects a read probe naming a tool outside that allowlist.

The bearer the test presents is the one a client resolves through mcp.credential, via the new mcp_client_bearer lookup over resolve_credential, which moved to utils.roles.applications.mcp so it is the same function mcp_servers renders every client connection with. The probe presented whatever the calling role passed, which for the fifteen adapter-fronted providers is a different secret than any client holds.

Sidecar isolation is read from the engine rather than measured inside the container, so it no longer needs a delegation to the node holding the task: attachment is compared by network name, which drops the gwbridge special case, and a swarm worker says it cannot measure instead of skipping silently.

Retrying is bounded by wall clock against MCP_DEADLINE_SECONDS, which scales through the same lookup('timeout') as the harness budget it has to stay under. A flag that is neither true nor false aborts rather than reading as off.

mcp-provider-probe becomes mcp-provider-cli-test: an enabled provider must ship the test rather than include the task. mcp-probe-bearer becomes mcp-adapter-bearer, tying ADAPTER_BEARER to the declared credential key now that no probe include carries it. A new integration test renders the shared env template for every provider through Ansible's own templar, so a later change to it breaks visibly instead of poisoning twenty deploys quietly.

Hermes and OpenClaw keep their client-side proof as CLI tests of their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 20:46:26 +02:00
kevinveenbirkenbach 0ff7368786 docs(requirements): record what was proven and correct two blockers
025 closes baserow, gitlab and the public-exposure limits. The gitlab
entry replaces the survey's Premium-or-Ultimate claim with what the
source says, because that claim would have kept a working endpoint
behind a licence that does not gate it.

032 turns two Phase 2 criteria from untouched into stdio_only with the
evidence: OpenClaw's own CLI reference calls 'openclaw mcp serve' a stdio
server whose process the client owns, Hermes runs the SDK's stdio
transport from both entry points, and neither repository defines an HTTP
server path. A stdio server has no address on the container network, so
the supervisor flow is blocked rather than merely unbuilt. The agent
identity entry stops waiting on a credential model this repository would
write: no deployed provider offers the per-user OAuth that 035 requires
before auth_subject: user may be declared.

035 closes the range half with measured numbers and records the mutating
criterion as a gate, since no surface enables mutations and a test of
that path would assert against nothing.
2026-08-29 11:10:08 +02:00
kevinveenbirkenbachandClaude Opus 5 2d30681977 docs(mcp): turn the audit into something the lints can check
The audit read as prose, so a role could sit under a classification nobody had checked against its own declaration, and "blocked" could not be told apart from "nobody looked". The sections are now named after the exact values a role declares, which makes the section titles the vocabulary: one rule holds every role under its own declared classification, a second rejects a classification outside the vocabulary, and a third requires a named blocker per blocked role while forbidding a blocked role from shipping an enabled surface.

The report itself is generated from the roles' own metadata and a drift check imports the generator and compares against the committed file, which caught a stale report twice while this work was in progress. It lives under docs/contributing rather than beside the requirements, because a generated file in that directory reads like a finished criterion.

The criteria across seven requirement files now carry what they rest on: what closed them, or what blocks them and where that was measured. Two coverage limits stay stated rather than hidden: the report is built only from roles that declare an MCP block, so a role blocked without one is invisible to it, and a ticked criterion here means implemented and tested rather than proven in operation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 00:58:44 +02:00
kevinveenbirkenbachandClaude Opus 5 b0d1d41564 docs(requirements): point at the paths the repository actually uses
031, 032 and 033 named meta/schema.yml as the place credentials live, which
481666bdb renamed to meta/secrets.yml. 035 named role scripts without their
language folder, which dfe7a7fdd introduced.

Not cosmetic: a wrong location in a requirement costs real time. 036 asserted
that .running and group_names agree on a single-host round, and acting on
that claim configured every AI consumer with the openrouter/auto vendor model
until the gateway rejected it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 12:42:58 +02:00
kevinveenbirkenbach b274c378ce docs(mcp): record that allowed_consumers creates no network edge
Two declarations appeared to govern reachability and only one did. A
client reaches a provider when the provider declares an mcp_client
consumer in meta/networks.yml, or when the client already carries that
provider as a service for an unrelated reason. Admission alone attaches
nothing.

Measured rather than reasoned: a Hermes deploy fails at its own probe
against the Prometheus adapter because the two join disjoint networks,
and Open WebUI reaches Baserow only through an unrelated service
declaration.
2026-08-10 00:12:13 +02:00
kevinveenbirkenbach f99ce04ddb docs(mcp): record when a server may act for the caller
The schema lint demands a delegation block whenever a role claims to act on the
caller's behalf, pinning refresh, revocation and audience binding at one exact
version, because a rendered deployment bearer is a service account whatever the
metadata says. The lint can reject a missing block but cannot say why those
three facts and no others, so the design note carries that.

The requirement is brought up to date, and the agent cheatsheet gains the two
worktree skills.
2026-08-06 21:31:20 +02:00
kevinveenbirkenbach 2b3f568e3e docs(mcp): add 035 proxy expansion audit
Adds docs/requirements/035-mcp-proxy-expansion.md with the exhaustive
169-role MCP audit: architecture decision, target metadata contract,
identity/authorization/credential requirements, discovery and
reconciliation, Flowise 3.1.3 and n8n 1.95.3 integration paths, curated
adapter candidates, tool and adapter safety contract, acceptance
criteria, and implementation order.

Revalidates 025-mcp-role-integration.md against that audit: the tree
already carries 18 role-local mcp: blocks, so the empty-baseline claim
is dropped. Adds 'adapter' to the implementation precedence chain,
corrects the stale hosted_only classification on wordpress, odoo and
discourse, and unchecks the criteria that metadata presence alone does
not prove (real provisioned identity, application-scoped RBAC grants,
enforced proxy limits, runtime mutating-tool policy, openwebui and
flowise client provisioning, end-to-end real tool call).
2026-08-03 22:53:16 +02:00