a9f2c7209c left the placeholder in underscore emphasis, which mdformat
rewrites to asterisks on every run. It surfaced as an unrelated diff in
each later git status.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Decision 11 rejects an answer that mangles a protected term, and the catalog then keeps the entry untranslated: today 64 of the German docs catalog sit that way, every one of them a recorded refusal rather than a missing attempt. A refusal class is a property of the engine and the language pair, which is the reason this requirement gave the gateway a router in the first place, so the same request belongs with the next candidate instead of ending there. Decision 17 states that, decision 18 separates the three counts a decision can learn from, because an engine that is rejected on one pair is neither unreachable nor generally worse, and decision 19 adds the oracle that judges a translation against its source rather than against the catalog, since a fluent translation that means something else is invisible to every mechanical check already in place.
Three rows in the enforcement table and seven acceptance criteria follow from them. No gateway code is written yet; this is the decision the operator asked to read first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A static audit of every open requirement against the current tree found
45 acceptance criteria that the code already meets but nobody had
ticked. Each one was read against its file:line evidence before it was
ticked; criteria that need a live outcome ("login succeeds", "passes",
"deploys green") stay open even where the test exists.
- 028 semaphore (13): web root, image pin, database and LDAP wiring,
break-glass seed, persisted secrets, OIDC client and provider JSON,
external admin seed, OIDC flow spec, LDAP and native admin spec gating,
playwright.env coverage.
- 018 LDAP coverage (13): no role-local LDAP flag; bookwyrm, mattermost,
akaunting, discourse, flowise and shopware exempt with a recorded
reason; friendica, minio and openwebui carry LDAP-gated scenarios; the
three out-of-scope roles carry no stale key.
- 026 addon syntax (8): the meta/addons migration of odoo, wordpress,
mediawiki, joomla, matrix, discourse and pretix, and update automation
that rewrites only the version line.
- 029 checkmk (4): CSP header, oauth2 gate, X-Remote-User header auth,
README coverage.
- 017 biber coverage (3): the biber naming lint, no BIBER keys in static
or backend roles, the fusiondirectory audit.
- 030 jellyfin (2): CSP header, non-interactive LDAP and OIDC plugin
provisioning.
- 031 tor onion (2): onion and TLS lookup unit tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Under sso the stalwart web administration specs need the administrator
group grant 199f2a6d41, which lives on feature/store-i18n only, so
feature/designer cannot produce the green run that ticks the item. The
item now says who owns it: feature/store-i18n ticks it after a green run
of the stalwart spec with design, sso and email on its merged state.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
flowise ran its final state: deploy failed=0 with 10 passed 4 skipped,
palette failed=0 7 passed, gallery 24 of 24 views.
gitlab ran its final state on a fresh CNG v19.5.0 deploy; the admin area
is now asserted against the palette instead of an HTTP 200 (bac3308f75).
Closing sync failed=0, palette failed=0 8 passed, gallery 29 of 29 views.
bigbluebutton carries the configured title through Greenlight and the
palette into the meeting client (76e9441117); the rewritten vars ran in
a full deploy: failed=0 with 12 passed 3 skipped, palette failed=0 10
passed, gallery 27 of 27 views including the meeting.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two conflicts, both resolved by combining rather than by picking a side.
`roles/web-app-socialhome/tasks/02_design.yml`: designer restructured the pass,
moving the token request and the theme write into the new
`02_design/converge.yml` and adding a stat of the name-state file. HEAD still
carried the converge block inline. Kept designer's stat, dropped HEAD's inline
block: the merged body below the conflict already includes `converge.yml` and
reads `socialhome_name_marker`, which only designer defines, so keeping HEAD's
side would have duplicated the token request and left that variable undefined.
HEAD's `retries: 0` on the token request is deliberately not carried over; the
default of 30 retries covers a service that is not yet accepting connections,
which matters more here than failing fast on bad credentials.
`roles/web-app-fider/files/playwright/test-design.js`: the import line takes
both sides, since the merged body uses designer's `apiGetOnion` and HEAD's
`requireDotenvValue`. The env block keeps HEAD's `requireDotenvValue` form,
because designer's `|| ""` defaults are what
`tests/lint/ansible/.../test_unguarded_env_default.py` exists to reject, and
drops HEAD's module-scope `title` and `logoEnabled`: the merged body defines
`title` locally and reads `DESIGN_LOGO_ENABLED` directly, so both were dead and
would fail `no-unused-vars`.
Checked after resolution: no conflict markers (`git diff --check`), no `nocheck`
line dropped by the merge, and no duplicated YAML key in the seven merged files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A role-level `placement: manager` pins every service of the role and keeps
every volume node-local, so a role needing one node-bound service had to give
up NFS for all of them. GitLab's gitaly is that case: its repositories volume
is node-local (`nfs: false`, gitaly does not support NFS) and a reschedule
left the data behind on the old node.
`deploy.yml.j2` now resolves `services.<service>.placement` first and keeps the
role-wide list as the fallback, so gitaly carries the constraint alone while
webservice, sidekiq, workhorse, shell, migrations and gitlabmcp stay
schedulable and `gitlab_shared`, `gitlab_uploads` and `gitlab_builds` keep
their NFS backing. Role placement still comes from the primary entry, which is
what drives that backing.
Two things had to come with it. The swarm drill decided that a role is
manager-pinned by grepping the whole `meta/services.yml`, so a per-service pin
would have skipped GitLab's drain and reschedule steps while six of its
services still run on workers; it now reads the primary entry. And
`resource_filter` returns a present-but-null value rather than the hard
default, which rendered `node.role == None`, a constraint no node satisfies.
`test_no_placement_manager.py` now inspects every `placement` value instead of
only `manager`, requires the justification for each, and rejects a value that
is not a swarm node role, since a misspelled one hangs the converge wait
rather than failing the deploy. Requirement 023 records the shape.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Socialhome renames an existing household to an inventory title and back
since 92c1f78771, so both roles of the item honour the override.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Compose app rows no longer spend deploy time on WireGuard. The full-mesh
test runs on svc-net-wireguard's own row, the role deploys where its group
puts it, and production keeps WireGuard for every role.
BookWyrm ran its complete spec green on the final state, the SeaweedFS
avatar upload included. Fider shows the generated logo with the object
store enabled since d912664538.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OpenTalk's conference room, both theme settings, the anchored title and the lockup's dark rule are covered and asserted; SuiteCRM's shell tab carries the design title, its role stylesheets hold no literal color, and its SAML specs ran with the design (the logout flake under the login wizard is recorded in its TODO and independent of the design). Each fix failed its negative control first and passed two closing role syncs, the blue palette check and a full gallery capture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both roles now carry the design title through their own template or script hook and assert it; each fix failed its negative control first, passed two idempotent closing role syncs, the blue palette check and a full gallery capture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each role's fix is committed and proven by a closing role sync without failures and a green spec; every role except prometheus also has a negative control that failed before its fix. Prometheus needed no change: a fresh complete run closed the stale-run reason.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The last two criteria closed on fork run 38836531620, a manual steer of
svc-db-redis#0 in compose with the vpn axis enforced. Its job log shows
INFINITO_COMPOSE_VPN=true reaching the step, svc-net-wireguard deployed beside
the app and skipped on the CI host, which holds no mesh, and the CLI test of
svc-net-wireguard writing two full meshes of two members each. The inner play
passed four handshake gates and four MTU probes, and the test printed all six
reachability assertions: I and II reach each other, II and III reach each
other, I and III do not.
With nothing left open, make requirements-archive moved its heading into the
README and deleted the file.
Compose hosts of one inventory join full meshes that each role opts into
through its networks.meshes, with no swarm manager and nothing hardcoded into
a role. The vpn axis decides per CI row, on a role's smallest variant only,
and a CLI test proves the shape on three throwaway hosts: I and II reach each
other, II and III reach each other, I and III do not.
Ticked with evidence from the local lab and the fork's CI runs: both VPNs
including the backup host, swarm advertising on the mesh, the prologue and
both passes over the mesh, key rotation converging, the mesh-off arm, the
axis rotation, pin and title, the measured MTU, nothing naming a mesh, the
three-mesh example, the role installing its own tooling, the NFS mount over
the mesh, the controller reaching the hosts it deploys over, the gate, and
swarm control, overlay and NFS carried over the mesh.
Dropped: rotating over a live mesh connection, which the prologue avoids by
rotating before the transport moves; the role being inert in compose, which
the backup host now contradicts; the failing-mount half of the NFS
criterion; and the port-reachability criterion with its claim in the user
story.
With nothing left open, make requirements-archive moved its heading into the
README and deleted the file.
Home Assistant, OpenCloud, Snipe-IT, Mastodon, Matrix, Moodle, PeerTube, Zammad and OpenClaw ran green on their final state.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Six conflicts, all from git pairing store-i18n's renamed roles with their designer originals.
web-app-meta/meta/services.yml and web-app-sphinx/vars/main.yml: store-i18n renamed web-app-sphinx to web-app-meta and dropped its custom build, so the role keeps the prebuilt ghcr.io/infinito-nexus/meta image and the old sphinx role stays deleted.
svc-ai-tika/templates/compose.yml.j2: a bogus rename pairing dropped sphinx build lines into the tika service. Rejected; the template is byte-identical to its pre-merge state again.
web-app-docs/templates/Dockerfile.j2: the designer file renders sphinx under the design title and reads SPHINX_IMAGE, SPHINX_OUTPUT_DIR and SPHINX_EXEC_DIR, none of which exist in web-app-docs, which builds from files/Dockerfile. Rejected.
web-app-docs/templates/playwright.env.j2 and files/playwright/test-design.js: both sides add independently, so both are kept. Of the designer's three new env reads only designTitle survives; iconUrl and faviconUrl are shadowed inside the test by stricter requireDotenvValue reads and were dead at module scope.
plugins/lookup/email.py merges clean toward designer, which is intended rather than a loss: 08ef66d8de (2026-10-08) replaces the submission_via_relay port-25 rule of 233f15f3e0 (2026-07-01) with per-bot app passwords under SSO, and carries the matching 04_manage_user/app_password tasks.
Checked after resolving: no duplicate YAML keys across the 17 merged YAML files, and no nocheck line dropped above a renamed key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Decidim, KIX, Jellyfin, Jenkins, Listmonk, pgAdmin, MediaWiki, Penpot, Pretix and YOURLS ran green on their final state. The inventory title override item now records that Decidim renames an existing organization to an inventory title and back, and keeps Socialhome's first-boot limit open.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
allows() is called once per /v1/chat/completions and nowhere else, so the 60s cache saved two Keycloak admin GETs on a request that then runs an LLM inference. On an authorization decision that is not a trade worth making.
It produced the CI failure directly. The spec walks hermes then openclaw: it grants biber only hermes, the hermes completion re-reads and stores {hermes}, then it swaps the grant to openclaw. Open WebUI sees the new group on the fresh sign-in, which is why the models assertion passes, but about eight seconds later the openclaw completion hits a fresh cache entry, reads {hermes} and refuses. A hit returned the value without re-stamping the timestamp, so all three attempts - 13.9s, 15.2s, 15.2s - stayed inside one 60s window and failed identically.
The same cache was a security defect in the other direction: a revoked caller stayed entitled for up to a minute, contradicting requirement 037's own sentence that the broker refuses the request and stops the agent once a caller has lost the group. Step 3 of that document now says the groups are resolved on every completion, which is what the code does. The admin-token cache is untouched - that is a credential lifetime bounded by expires_in, not an entitlement answer.
Verified with a negative control: the CI scenario run against the real access.py through a transport fake, alongside a transcription of the pre-fix groups_of over the same transport. Pre-fix, the swapped-group check returns False, which is the CI failure; post-fix it returns True, and a revocation between two checks is seen by the second.
The repository had already worked around this twice: files/test/test.sh slept 65 seconds after granting a membership and membership.py constructed the client with the cache disabled. Both are removed.
The group-to-agent mapping was not at fault - broker and spec derive the path from the identical rbac_group_path expression, and the sibling spec that holds both groups passed.
Each completion now costs two Keycloak admin round trips, unmeasured on an onion deployment where KEYCLOAK_URL is a .onion against a 15s per-call timeout. The unit test that asserted the cached answer is replaced by two that encode the CI failure. Requirement 037 still has 47 translated catalogs carrying the removed access_cache_seconds row; the i18n tests pass, so it is obsolete rather than broken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both roles are ticked on their completed passes: nextcloud mirrors its theme picker and asserts admin theming and the rendered logos, odoo carries both theme sheets on tokens with working focus rings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
baserow, espocrm, littlejs and socialhome are ticked after their specs ran green on the image versions main brought in; espocrm's run includes the switched-off news panel. hugo, mini-qr and pixelfed are ticked on their completed passes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Decision 9 now states that feature/designer reaches main through the draft PR of feature/store-i18n, which merges it round by round, and the matching criterion is reworded. The design-off criteria and fifteen role items are ticked on their verified passes. openwebui and postmarks stay open with their evidenced upstream or license limit, espocrm with the decided but not yet implemented iframe switch. socialhome, baserow and littlejs reopen because the image bumps from main came after their last run, and stalwart because its web administration specs under sso need the administrator group grant of feature/store-i18n.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Checked: every role ships its mesh fields empty, one module reads them, and
the three-mesh layout deployed svc-dns-unbound end to end on the local lab
with the same members and addresses as before the move.
The multi-mesh spoke criterion is checked: the three-mesh example deployed
svc-dns-unbound end to end on the local lab with the NFS server up on beta and
gamma. The other two stay open until the CI dispatch has run.
An audit checked all 77 role items against the role criterion and an
independent skeptic challenged every tick: frontend and backend styled,
colors only through --design-* tokens, logo and title through the app's
own mechanism, spec assertions for tokens, contrast, both modes, the
native toggle, logo and title, and a green complete run on the final
state. Nine roles hold. The other 68 name the reason they stay open,
most often a later change that no run has exercised yet: the vendor
selector split 6c2de017f8, the logo lockup colors 63f3631567, or an
image bump from main.
The base items for design off with javascript on, disable=design, the
title override of Decidim and Socialhome and the draft PR of the branch
stay open with their reason. The implementing PR is #625 of
feature/store-i18n.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A lockup slot on a page or panel drew its title in the light
--design-primary with a --design-on-primary outline in both color schemes.
With an orange base that is about 3:1 on the light surface, and the SVG
never changed in dark mode; the Sphinx, Jenkins and Jellyfin passes
reported it.
sys-front-inj-design now passes --design-link with a --design-surface-1
outline for light and their dark values for dark. render_assets() hands
the dark pair to every slot without frame: true, and render_svg() adds a
style rule under prefers-color-scheme: dark that overrides the fill and
stroke attributes. Slots on brand chrome keep the frame colors, and the PNG
keeps the light pair. The design_assets action requires the two new
arguments; its two callers, assets.yml and Keycloak's theme task, pass
them.
A unit test asserts the dark rule on page slots and its absence on frame
and logo-only slots; it fails when the dark pair is not handed on. In
headless Chromium an <img> of the SVG renders the title #825200 on #fff5ea
in light and #ffa500 on #140e07 in dark.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The design queue skipped a hand-kept UI_LESS_ROLES list in
cli/meta/roles/design. The decision now lives in each role: a web-app- or
web-svc- role without a UI to design sets design.enabled: false in its
meta/services.yml, and design_disabled() reads that flag. web-svc-design,
the provider, stays out by its entity name.
A new lint, test_design_off_reason.py, requires a design entry in every
web-app- and web-svc- role and, where the entry is switched off, a
"# nocheck: design-off Reason: <text>" on the design: line or the comment
lines directly above it. The rule is in the suppression catalog, and
requirement 037 decision 13 describes it.
Twelve roles switch design off with a reason checked against their code:
asset and simpleicons were already off; file, onlyoffice, bridgy-fed and
mig were on and no longer receive the shared CSS and design script; cdn,
collabora, coturn, legal, mirror and xmpp had no entry. The four that were
on drop their bond and their design variant overrides, which the
disabled-bond and variants-static-override lints reject on a literal false.
The queue lists 77 candidates; HEAD's list also counted bridgy-fed, mig,
collabora and onlyoffice. Cosmos diagrams and the integration matrix are
regenerated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Five roles handed a credential to `container exec` as `-e NAME=<value>`, or
as a positional argument. An argv element reaches the docker client's command
line, the process list, and the task result's `cmd` field, which ansible prints
from -v upward. Local deploys run with MASK_CREDENTIALS_IN_LOGS disabled, so the
retained `no_log` does not mask them there.
Where the container already carries the value, the flag added exposure and
nothing else, so it is gone rather than rerouted. `container exec` runs in the
container's own environment, and sys-svc-container's compose base wires every
role's rendered env file as `env_file`:
web-app-akaunting ADMIN_EMAIL, ADMIN_PASSWORD templates/env.j2:27-28
web-app-baserow SECRET_KEY, BASEROW_SECRET_KEY templates/env.j2:8-9
The rest are values no container holds, so they travel through the task's
`environment` and a valueless `-e NAME`, which the docker client resolves from
its own environment: baserow's MCP_ENDPOINT_KEY and BOOTSTRAP_ADMIN_PASSWORD,
pretix's MCP_TOKEN, and home assistant's HA_PASSWORD, HA_SERVICE_PASSWORD and
HA_TOKEN. Every script already read these names through os.environ, so none of
them changed.
web-app-homeassistant loses tasks/utils/mcp/realign_owner.yml rather than
having its secret rerouted. It held the owner password as a positional of
`hass --script auth`, and it is unreachable: 00_core.yml:32 includes
utils/owner.yml before utils/mcp.yml at :35, owner.yml ends in a hard assert on
`homeassistant_owner.rc == 0`, and that register comes from the same
provisioning script with the same HA_USERNAME and HA_PASSWORD. A hub that
refused the owner password therefore fails the play before the MCP tasks run,
so the LOGIN_REFUSED branch that was this file's only caller cannot be taken.
tasks/utils/owner/realign.yml is the remaining realign path and passes its
arguments as JSON on standard input. The two LOGIN_REFUSED clauses in
mcp/provision.yml go with it, because `rc != 0` is what they now mean.
files/python/provision_mcp.py becomes provision.py: owner/provision.yml runs it
with HA_OWNER_ONLY=1, so it has provisioned the owner since that task existed
and the name covered half its job.
Two co-located defects in the same tasks, which cannot be split from the
transport change at file granularity:
web-app-akaunting matched `'CHANGED' in stdout`, which is also true of the
`UNCHANGED` the snippet prints when the password already matches, so the task
reported a change on every deploy. It now matches the whole output line.
`expand_argument_vars: false` guards the argv because one element carries the
PHP snippet: ansible's expandvars runs over every element, and a `$name` the
environment happens to hold would be substituted into the code. Measured as a
no-op on today's content, since expandvars leaves an unknown name alone.
web-app-pretix read its result from `stdout_lines[-1]`, which is IPython's exit
prompt. `pretix shell_scoped` runs the script through IPython, which executes an
indented block only once a dedented line follows, so the block that created the
token was left unrun when it ended the input. The script's last statement is now
a top-level print of a marker, which forces the block before it to execute, and
the task matches `(?m)^PRETIX_RESULT=CHANGED`. The end anchor is left off on
purpose: a trailing carriage return defeats `$`, and a literal backslash would
have to survive both YAML and Jinja.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A gallery run took every view twice, once with the injected snippets
stripped. The comparison is only needed when the operator asks for it
for one role, so captureDesignGallery now captures the "after" side
alone and adds the plain side when PLAYWRIGHT_GALLERY_BEFORE is true.
make design-gallery passes it through as before=true; gallery.sh
refuses any other value, and rerun-spec.sh hands the variable into the
Playwright container. The deploy-driven run sets no such switch, so it
captures the designed side only.
The design loop page and the requirement describe the gallery as the
designed state and name before=true for a comparison.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>