Add user and group QQQ application-role access editing, bounded catalogs, webhook inbox and SQL mirrors, refreshed session permissions, and deferred invitation assignments. Include the isolated local review host, browser acceptance scripts, documentation and the token-widget column mapping fix. User approved this RC checkpoint. Full 1.0 gates remain documented, including the schema-drift matcher and PostgreSQL integration errors from the full gate. Companion UI: QRun-IO/qqq-frontend-next#205. Co-Authored-By: GPT-6 <noreply@openai.com>
8.4 KiB
AGENTS.md / CLAUDE.md
Shared guidance for agents working in this repository. Keep AGENTS.md and CLAUDE.md in sync.
Project Overview
QBit Authentik: a QQQ QBit to manage Authentik users, groups, memberships and group-granted QQQ permissions from a QQQ app. Authentik's database is the source of truth; the qbit reads live and writes through the Authentik API (never its DB). Design: docs/DESIGN.md (approved spec, QRun-IO/qqq#1065). Current line: 0.1 = the full user lifecycle and group/permission management (phases 0-3), read-only by default.
Build Commands
./mvnw compile # always use the wrapper: Maven 3.10 cannot read the parent POM
./mvnw test # unit tests, offline, on recorded fixtures (src/test/resources/fixtures/2026.8.3)
./mvnw test -Dtest=ClassName
./mvnw verify -DskipITs # compile, unit tests and JaCoCo; no contract ITs
CI=true ./mvnw verify # + contract ITs: Authentik 2026.8.3 server+worker, Postgres, Mailpit (Testcontainers), JaCoCo gate
./mvnw verify -Dit.test=QbitReadContractIT -Dtest=none -Dsurefire.failIfNoSpecifiedTests=false
./mvnw verify -Dauthentik.fixtures.record=true # refresh unit fixtures from the contract ITs
./mvnw test -Dtest=SchemaDriftTest -Dauthentik.schema.file=/path/to/schema.yml # drift vs another release
- Contract ITs (
*IT, packagecontract) share oneAuthentikTestStackper JVM; first boot takes ~1.5-3 minutes.@ExtendWith(RequiresDockerCondition.class): skipped without Docker locally, failed withCI=true. - The stack boots with
src/test/resources/blueprints/*.yamlalready in/blueprints/custom: that IS contract test (h) (bootstrap on an empty instance). TestApiuses HTTP/1.1 on purpose: Java's HttpClient tries an h2c upgrade on plain-http POSTs, which Authentik answers with "400 Invalid HTTP request received."- Tests that grant extra permissions to the service-account role must remove them again (they are additive and shared).
Architecture
AuthentikQBitProducervalidates the config, registers anAuthentikRuntimefor the API backend, then runs the component producers inmetadata/.AuthentikRuntime(registry by API backend name): config,TokenBucketThrottle(10 rps, burst 20 by default),AuthentikGuard,AuthentikFilterMapper, response cache, and the table-name toAuthentikTableSpecmap.client/AuthentikActionUtil extends BaseAPIActionUtilis the only code that talks to Authentik.doQuery/doCount/doGetuse page/page_size paging (max 100). Key-only filters become per-id GETs. The user scope and type filters are applied server-side and re-checked per record.- The generic table writes (
doInsert/doUpdate/doDelete) are refused. Changes usewrite(WriteAuthorization, ...)orwriteAs(bearer, WriteAuthorization, ...), and onlyAuthentikGuard.authorize(AdminAction)mints aWriteAuthorization(an architecture test pins callers toAuthentikAdminand itsAuthentikTokensdelegate). execute()is the single request loop: throttle, bearer header, 429/5xx retry, error mapping, logging.send()is the test seam (FixtureActionUtil,QbitReadContractIT.RecordingActionUtil).
client/AuthentikTablesmaps tables to the pinned schema (users, groups, sessions, events), plus the virtualgroupMembertable, whichAuthentikActionUtil.memberships()serves from a group, a user, or a bounded sweep.client/AuthentikApiSurfacelists every operation and parameter used.SchemaDriftTestandSchemaDriftITcheck that surface.client/AuthentikFilterMappermaps a QQQ filter to Authentik params, or rejects it with aQUserFacingException. It never filters client-side.security/AuthentikGuardhandles read permission checks, write authorization, the user scope, and protected-account detection.security/IdentityPermissionis theauthentik.*catalog (session form<name>.hasAccess).admin/AuthentikAdminplans and performs every change (AdminAction,AdminRequest,AdminPlan): the guardrails of DESIGN.md 5.4 run against LIVE Authentik data and the operator's LIVE groups (never the session claim) at plan time and again at execute time; the audit row (AdminActionAuditor, tableauthentik_admin_action) is written BEFORE the call and the call is not made if that fails; then the change, then an Authentikcustom_qqq_admin_actionevent.AuthentikSafetyholds the pure rules (subset rule helpers, last-admin, fingerprints). Grantingauthentik.*queues anauthentik_approval_requestfor a second person (approve/reject).admin/AuthentikTokensimplements DESIGN section 13: personal calls use an action-scoped exchanged bearer; service-account calls use the qbit bearer. One-time keys never enter records, process values, audit or event context.TokenKeyDeliveryrefuses key-producing actions when core has no response-only secret channel.processes/AuthentikProcesses: every action is a standard streamed-ETL table or record action (details screen, Review = dry run, Result), never in the nav. The ETL defaults to full validation, so the Review screen already holds the dry-run lines. The preview step's record count is NOT carried to later steps (and a client count needssetDistinctCount), so steps count targets themselves.app/:AuthentikHomeData/AuthentikHomeView(the single home widget, HTML with no tables) andAuthentikRelations(joins, child-list widgets and sections so sub-tables appear on their parents, not in the nav).permissions/GroupGraph+EffectivePermissionResolver(pure) turn groups and their ancestors into QQQ permissions. The util caches the graph forgroupCacheTtlSecondsand uses it for derived fields.drift/AuthentikDriftReportholds the drift checks, using only the service account's read permissions. It is rendered bymetadata/AuthentikDriftReportWidgetRenderer, a standard TABLE widget gated byauthentik.admin.
Invariants (do not break)
- Every
AuthentikActionUtilentry point calls the guard first (in addition to core action permission enforcement, retain Authentik-specific rules and protection for direct util calls). - Never log query strings (search terms are PII), the token, successful GET bodies, or bodies of sensitive endpoints; run anything stored through
AuthentikRedactor. - Never filter client-side; reject what Authentik cannot evaluate.
- Multi-value params are repeated plain names (
type=a&type=b). Authentik silently ignorestype[]=. - The user detail GET takes no
include_*params. Groups have nolast_updatedin 2026.8. check_access?for_user=is useless for the service account: it is silently evaluated for the caller. Never use it for other users.- Only
AuthentikAdminand itsAuthentikTokensdelegate may callAuthentikActionUtil.write/writeAs. Every write needs a reason, passes the guard, and is audited first. recovery_emailneeds the brand to have a recovery flow ("No recovery flow set." otherwise), the role'sadd_user(DRF maps POST to add) plusreset_user_password, andview_emailstageon the stage.- The service account must never be a superuser and never hold
enable_group_superuseror impersonate permissions; never token-key READ (view_token_key); add_token and set_token_key only for service-account creation and rotation (add_token cannot mint for others);delete_user/delete_grouponly when the host turns theDELETEfeature on.
Code Style
3-space indentation, braces on the next line, return (x);, flower-box Javadoc on every class and method, fluent withX() setters, wrapper types in config.
Dependencies
Java 21. Parent qbit-build-parent:2.1.0. The default-active qqq-snapshot profile imports qqq-bom-pom:4.1.0-RC.21 (ADR-0007, as in qbit-sql-workbench). The project uses qqq-backend-core and qqq-backend-module-api. Test dependencies: JUnit 5, AssertJ, Mockito, Testcontainers 1.21.4, and Jackson YAML (from qqq) for the schema tests.
Release
Gitea is primary. .gitea/workflows/qrun-release.yaml delegates to the shared
qrun-release-v1 lifecycle; .qqq-ci.yml identifies develop as the default
feature branch and enables snapshot, RC and release publication. Do not infer
feature completeness from main, which still contains the older baseline.
Local review
Run ./scripts/Start-Review.ps1 on Windows with Java 21 and Docker Desktop.
The Next UI is at http://localhost:18300/app/authentikAdmin; startup URLs for
Authentik and Mailpit are in target/review-server.log. All application ports
bind to loopback. See docs/LOCAL-REVIEW.md for scope, reset and limitations.