CLAUDE.md and GEMINI.md existed as per-runtime extensions of AGENTS.md, and only one of them carried anything at all: GEMINI.md stated that it had no additions, while CLAUDE.md held rules that bind every runtime anyway — the permission announcement, the make-over-raw preference, the loop, chain and heredoc bans, the stream-the-log-to-/tmp rule, the CI-evidence rule, the push ban and the comment rule. A rule that binds every agent does not belong in one agent's file. AGENTS.md absorbs them and now states that no tool-specific extension file exists; agents whose runtime auto-loads a different filename reach it through their own configuration instead. The filenames were machinery as much as prose. The PR templates, the branch and pull-request scope tables, the settings and security docs that justified the loop deny by "the textual CLAUDE.md rule", six requirement documents whose push instruction linked ../../CLAUDE.md, and a test docstring citing the shell-loop ban all named them, and now name AGENTS.md. scripts/meta/resolve/pr/scope.sh deliberately keeps matching CLAUDE.md and GEMINI.md: this change deletes them, and without those patterns the change itself would not classify as agent-scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.5 KiB
023 - Setup Penpot
User Story
As a Infinito.Nexus platform operator, I want to provide a fully integrated, production-ready Penpot design and prototyping platform within the Infinito.Nexus ecosystem so that teams can create, collaborate on, and share design projects directly inside their sovereign Infinito.Nexus installation.
Context
Penpot is an open-source design and prototyping platform (a self-hostable Figma alternative). This requirement tracks a new web-app-penpot Ansible role that deploys Penpot's containerized stack and wires it into the Infinito.Nexus ecosystem (identity, reverse proxy, marketplace, backup).
The role MUST follow the standard role-meta layout described in layout.md (meta/services.yml, meta/secrets.yml, meta/volumes.yml, meta/variants.yml, meta/info.yml, meta/main.yml). web-app-openproject is the closest structural model — it combines PostgreSQL, a Redis cache service, background workers, an internal proxy, Keycloak SSO and LDAP — and SHOULD be used as the primary reference; web-app-taiga and web-app-confluence are secondary references.
Penpot's upstream stack is frontend + backend + exporter + PostgreSQL + Redis. The exporter container is mandatory: it backs the export (SVG/PDF) and developer-handoff features required below.
Acceptance Criteria
Role & meta layout
- A new role exists at
roles/web-app-penpot/and follows the role-meta layout in layout.md. meta/services.ymldeclares every container service and alifecyclekey (starting tierbeta).meta/secrets.ymldefines the role's configurable variables.meta/volumes.ymldeclares the role's named volumes.meta/variants.ymldeclares the matrix-deploy variants (see Variants below).meta/info.ymlandmeta/main.ymlare present and valid.
Compose stack
- The stack includes a Penpot frontend container.
- The stack includes a Penpot backend container.
- The stack includes a Penpot exporter container (required for SVG/PDF export and developer handoff).
- PostgreSQL is provided via
svc-db-postgres(declared through thepostgresservice block, not a role-local database container). - Redis is provided via the
redisservice block inmeta/services.yml(auto-emittedsvc-db-redissidecar; service nameredis). - All container volumes, env vars, ports and healthchecks follow Infinito.Nexus conventions.
Identity integration
- OIDC login is available via
web-app-keycloak, enabled throughPENPOT_FLAGS(e.g.enable-login-with-oidc). (verified live against a Penpot + Keycloak deploy.) - LDAP login is available via
svc-db-openldap, enabled throughPENPOT_FLAGS(e.g.enable-login-with-ldap). (verified live against a Penpot + OpenLDAP deploy.) - Native local email/password login is off under OIDC (
disable-login-with-passwordforces users through SSO) and on otherwise, derived from thessoservice flag (variant-aware); when on, the role bootstraps a local password for theadministratorprofile via the backend PREPL (enable-prepl-server) intasks/main.yml, and the bootstrap is skipped under OIDC. - Self-registration is on when OIDC is enabled (Penpot's OIDC JIT provisioning routes through the registration path) and off otherwise, derived from the
ssoservice flag, renderingenable-registration/disable-registrationinPENPOT_FLAGS. - OIDC and LDAP configuration is fully automated via Ansible (env vars / templated config), requiring no manual post-deploy steps.
CSP & reverse proxy
- The role generates a correct Nginx/CSP vhost using csp_filters.py and the
nginx_vhostlogic (viasys-stk-full+meta/server.ymlCSP flags). - The vhost configures WebSocket upgrade so real-time collaboration (live cursors, comments) works (handled by the shared proxy template).
- The vhost supports HTTPS termination and routes to internal service names.
Variants
meta/variants.ymldefines an all-on variant pinning every dynamic services flag totrue.meta/variants.ymldefines an all-off variant pinning every dynamic services flag tofalse.meta/variants.ymldefines an LDAP-only variant (ldapon,sso/OIDC off) per the convention in web-app-openproject/meta/variants.yml.
Playwright coverage
- A per-role Playwright spec exists and gates authenticated scenarios via
skipUnlessServiceEnabledper 006 - Service-gated Playwright tests. - A separate OIDC login scenario and LDAP login scenario exist (not combined into one test body) per 018.
- Both the canonical admin persona and the non-admin RBAC persona
biberare exercised per 017. - A native (local-password) admin login scenario exists (
test-login-native.js), separate from the OIDC/LDAP companions, and is skipped whenssois enabled (native login disabled under OIDC). - A scenario verifies project creation.
- A scenario verifies asset upload.
Infinito.Nexus integration
- A marketplace entry for "Penpot Design" is added and correctly categorized (auto-discovered
web-approle withmeta/info.yml+galaxy_tags). - The role declares menu tags:
design,penpot,prototyping,collaboration,ui-ux,figma-alternative. - Desktop and mobile launcher URLs are generated automatically from the role's domain.
- PostgreSQL data is backed up via the existing backup role(s).
- The asset-storage volume is backed up via the existing backup role(s).
Storage
- A named volume is configured for asset storage (design files, images, fonts) and is backup-ready.
- Volume mounts follow Infinito.Nexus conventions.
- S3-compatible object storage is documented as a future option in the role README (not implemented).
Definition of done
- Penpot deploys successfully with one command on a fresh host.
- The app is reachable at its generated canonical domain (
penpot.design.{{ DOMAIN_PRIMARY }}, e.g.penpot.design.infinito.nexus). roles/web-app-penpot/README.mddocuments the role's purpose, configuration, and the S3 future option.make testpasses with the new role in place. (Penpot-relevant lint + integration suites verified green on host; full in-containermake testrequiresmake setup/sudo not available in the sandbox.)
Procedure
The implementation of this requirement MUST be executed autonomously by the agent. The following rules apply for the entire run and are non-negotiable:
- Clarifying questions only at the start. Any open question, ambiguity, or missing decision (e.g. Penpot image versions, exporter resource limits,
PENPOT_FLAGSset, lifecycle starting tier, secrets source) MUST be raised once at the very beginning of the run, in a single batched round, BEFORE any file is changed. Ambiguities discovered mid-run MUST be resolved by the agent using its best judgement, recorded in the role'sREADME.mdor a code comment, and revisited only at PR review. - Iteration loops. The agent MUST follow the Compose Loop for every change inside
roles/web-app-penpot/, and the Playwright Loop for every change to the role's Playwright spec. The debug-locally step MUST NOT be skipped in favour of remote CI reruns. - No
askprompts mid-run. The agent MUST NOT trigger any tool call that routes throughpermissions.askin .claude/settings.json during implementation. Where a tool would otherwise route throughask, the agent MUST select an equivalent covered bypermissions.allow, or rephrase the operation to fit the sandbox. The single permitted exception is the final commit. - No interruptions. Bug fixes, deploy failures, lint failures,
make testfailures, and healthcheck flaps MUST be resolved at their root inside this same iteration without prompting the operator. Workarounds, ad-hoc skips, retry-until-green loops, or "track in a follow-up" deferrals MUST NOT be used. - One commit at the end. ALL changes (role, meta, Playwright spec, documentation, marketplace entry, and the ticked checkboxes in this document) MUST be combined into ONE commit, created only after every Acceptance Criterion above is checked off (
- [x]) andmake testis green. The agent MUST NOT push; the operator runsgit-sign-pushoutside the sandbox per AGENTS.md.
See Also
- Upstream: Penpot Official Website
- Docker setup: Penpot Docker Guide (note the mandatory
exporterservice) - Requirements convention: requirements.md
- Role-meta layout: layout.md
- Structural model: web-app-openproject; secondary: web-app-taiga, web-app-confluence
- 006 - Service-gated Playwright tests
- 017 - Playwright biber RBAC coverage
- 018 - Playwright LDAP authentication coverage