resolves ${env.X} secrets itself (APIBackendMetaData.enrich() is off in QQQ)
keeps the token and webhook secret @JsonIgnore and redacted in toString()
refuses features that aren't built yet
AuthentikQBitProducer produces:
an API backend (CUSTOM auth, so no token in metadata)
read-only authentikUser / authentikGroup tables
the "Identity" app, which hosts can re-parent
AuthentikActionUtil (a BaseAPIActionUtil subclass) is the single path to Authentik:
page/page_size paging, with count from pagination.count
key lookups by GET, since users have no pk list filter
user scope: server-side params, re-checked per record
a shared token-bucket throttle (10 rps, burst 20)
429 (with Retry-After) and GET-5xx retries
redacted error mapping
OutboundAPILog rows without PII
AuthentikFilterMapper: AND-only and exact. Anything Authentik cannot evaluate (OR, sub-filters, NOT_*, unmapped operators, a second search term) is rejected, never filtered client-side.
AuthentikGuard:
identity.* read checks; sensitive fields need identity.audit.read
every write refused in 0.1
protected-account detection: the service account, superusers, qqq.io/protected, configured names
Tests
48 unit tests on recorded 2026.8.3 fixtures, captured and scrubbed by the contract tests.
Pinned schema src/test/resources/authentik/schema-2026.8.3.yml, fetched unauthenticated from auth.kof22.com/api/v3/schema/:
SchemaDriftTest checks every operation and parameter used. It already caught a spec error: the user detail GET takes no include_* params.
SchemaDriftIT diffs the running container's schema against the pin.
.github/workflows/schema-drift.yml runs the drift test weekly against the newest release.
27 contract tests (Testcontainers: Authentik 2026.8.3 server and worker, Postgres, Mailpit) booting the kof22-shaped blueprints:
IT
What it shows
BootstrapBlueprintIT
(h): bootstrap on an empty instance via metaapplyblueprint; SA, role (global + object permissions), break-glass, seed groups; open point (i)
SuperuserFloorIT
(b): every escalation route refused (ii). Pins that the lockout direction and self-minted tokens are not refused
BlueprintReapplyIT
(c): API edits survive re-applying both blueprints; a removed role permission is re-asserted. (d): present entries wipe members, memberships and attributes
AccessCheckIT
(iii): check_access?for_user ignored for non-superusers; policy test 500; app list filtered
The qbit's own read path end to end; records the fixtures
SchemaDriftIT
Running schema = pinned schema for the surface used
Docs
docs/PHASE0-OWNER-CHECKLIST.md: items 6, 7, 8, 10, plus the decisions surfaced
docs/cluster-gitops-phase0/: the lint and a local rehearsal script
docs/STATUS.md
Results
CI=true ./mvnw verify locally: unit 48/48, contract 27/27, JaCoCo gate met. The contract suite takes about 10 minutes, mostly Authentik's first boot and asynchronous blueprint applies.
Phase 0, agent items 2-4 of [DESIGN.md](../blob/develop/docs/DESIGN.md) section 11.0 (spec QRun-IO/qqq#1065, now revision 3).
## Contents
**Client and metadata** (read-only release line)
- `AuthentikQBitConfig`:
- validates its settings
- resolves `${env.X}` secrets itself (`APIBackendMetaData.enrich()` is off in QQQ)
- keeps the token and webhook secret `@JsonIgnore` and redacted in `toString()`
- refuses features that aren't built yet
- `AuthentikQBitProducer` produces:
- an API backend (CUSTOM auth, so no token in metadata)
- read-only `authentikUser` / `authentikGroup` tables
- the "Identity" app, which hosts can re-parent
- `AuthentikActionUtil` (a `BaseAPIActionUtil` subclass) is the single path to Authentik:
- page/page_size paging, with count from `pagination.count`
- key lookups by GET, since users have no pk list filter
- user scope: server-side params, re-checked per record
- a shared token-bucket throttle (10 rps, burst 20)
- 429 (with Retry-After) and GET-5xx retries
- redacted error mapping
- `OutboundAPILog` rows without PII
- `AuthentikFilterMapper`: AND-only and exact. Anything Authentik cannot evaluate (OR, sub-filters, NOT_*, unmapped operators, a second search term) is **rejected**, never filtered client-side.
- `AuthentikGuard`:
- identity.* read checks; sensitive fields need `identity.audit.read`
- every write refused in 0.1
- protected-account detection: the service account, superusers, `qqq.io/protected`, configured names
**Tests**
- 48 unit tests on **recorded 2026.8.3 fixtures**, captured and scrubbed by the contract tests.
- Pinned schema `src/test/resources/authentik/schema-2026.8.3.yml`, fetched unauthenticated from `auth.kof22.com/api/v3/schema/`:
- `SchemaDriftTest` checks every operation and parameter used. It already caught a spec error: the user detail GET takes no `include_*` params.
- `SchemaDriftIT` diffs the running container's schema against the pin.
- `.github/workflows/schema-drift.yml` runs the drift test weekly against the newest release.
- 27 **contract tests** (Testcontainers: Authentik 2026.8.3 server and worker, Postgres, Mailpit) booting the kof22-shaped blueprints:
| IT | What it shows |
|---|---|
| `BootstrapBlueprintIT` | (h): bootstrap on an empty instance via `metaapplyblueprint`; SA, role (global + object permissions), break-glass, seed groups; open point (i) |
| `SuperuserFloorIT` | (b): every escalation route refused (ii). Pins that the **lockout** direction and **self-minted tokens** are *not* refused |
| `BlueprintReapplyIT` | (c): API edits survive re-applying both blueprints; a removed role permission is re-asserted. (d): `present` entries wipe members, memberships and attributes |
| `AccessCheckIT` | (iii): `check_access?for_user` ignored for non-superusers; policy test 500; app list filtered |
| `SignInPolicyIT` | Attribute-driven sign-in policy; inert webhook plumbing |
| `QbitReadContractIT` | The qbit's own read path end to end; records the fixtures |
| `SchemaDriftIT` | Running schema = pinned schema for the surface used |
**Docs**
- `docs/PHASE0-OWNER-CHECKLIST.md`: items 6, 7, 8, 10, plus the decisions surfaced
- `docs/cluster-gitops-phase0/`: the lint and a local rehearsal script
- `docs/STATUS.md`
## Results
`CI=true ./mvnw verify` locally: unit 48/48, contract 27/27, JaCoCo gate met. The contract suite takes about 10 minutes, mostly Authentik's first boot and asynchronous blueprint applies.
Related drafts (do not merge yet):
- KofTwentyTwo/cluster-gitops#72 (blueprints)
- KofTwentyTwo/cluster-gitops#73 (backend env)
- the Website-Backend wiring
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Audit note (2026-10-12, AI-assisted): this PR commits a compiled Python cache file, docs/cluster-gitops-phase0/scripts/__pycache__/lint-authentik-blueprints.cpython-314.pyc. Suggest removing it and adding __pycache__/ to .gitignore. Also: develop's README links docs/PHASE0-OWNER-CHECKLIST.md and docs/STATUS.md, which only exist once this PR merges.
Audit note (2026-10-12, AI-assisted): this PR commits a compiled Python cache file, `docs/cluster-gitops-phase0/scripts/__pycache__/lint-authentik-blueprints.cpython-314.pyc`. Suggest removing it and adding `__pycache__/` to `.gitignore`. Also: develop's README links `docs/PHASE0-OWNER-CHECKLIST.md` and `docs/STATUS.md`, which only exist once this PR merges.
Addressed: 447d4b1 removes the committed __pycache__/*.pyc and ignores Python bytecode; the README links to docs/PHASE0-OWNER-CHECKLIST.md and docs/STATUS.md resolve once this merges. gitleaks over full history: no leaks. Squash-merging so the bytecode never reaches develop history.
Addressed: 447d4b1 removes the committed `__pycache__/*.pyc` and ignores Python bytecode; the README links to `docs/PHASE0-OWNER-CHECKLIST.md` and `docs/STATUS.md` resolve once this merges. gitleaks over full history: no leaks. Squash-merging so the bytecode never reaches `develop` history.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 0, agent items 2-4 of DESIGN.md section 11.0 (spec QRun-IO/qqq#1065, now revision 3).
Contents
Client and metadata (read-only release line)
AuthentikQBitConfig:${env.X}secrets itself (APIBackendMetaData.enrich()is off in QQQ)@JsonIgnoreand redacted intoString()AuthentikQBitProducerproduces:authentikUser/authentikGrouptablesAuthentikActionUtil(aBaseAPIActionUtilsubclass) is the single path to Authentik:pagination.countOutboundAPILogrows without PIIAuthentikFilterMapper: AND-only and exact. Anything Authentik cannot evaluate (OR, sub-filters, NOT_*, unmapped operators, a second search term) is rejected, never filtered client-side.AuthentikGuard:identity.audit.readqqq.io/protected, configured namesTests
src/test/resources/authentik/schema-2026.8.3.yml, fetched unauthenticated fromauth.kof22.com/api/v3/schema/:SchemaDriftTestchecks every operation and parameter used. It already caught a spec error: the user detail GET takes noinclude_*params.SchemaDriftITdiffs the running container's schema against the pin..github/workflows/schema-drift.ymlruns the drift test weekly against the newest release.BootstrapBlueprintITmetaapplyblueprint; SA, role (global + object permissions), break-glass, seed groups; open point (i)SuperuserFloorITBlueprintReapplyITpresententries wipe members, memberships and attributesAccessCheckITcheck_access?for_userignored for non-superusers; policy test 500; app list filteredSignInPolicyITQbitReadContractITSchemaDriftITDocs
docs/PHASE0-OWNER-CHECKLIST.md: items 6, 7, 8, 10, plus the decisions surfaceddocs/cluster-gitops-phase0/: the lint and a local rehearsal scriptdocs/STATUS.mdResults
CI=true ./mvnw verifylocally: unit 48/48, contract 27/27, JaCoCo gate met. The contract suite takes about 10 minutes, mostly Authentik's first boot and asynchronous blueprint applies.Related drafts (do not merge yet):
🤖 Generated with Claude Code
Audit note (2026-10-12, AI-assisted): this PR commits a compiled Python cache file,
docs/cluster-gitops-phase0/scripts/__pycache__/lint-authentik-blueprints.cpython-314.pyc. Suggest removing it and adding__pycache__/to.gitignore. Also: develop's README linksdocs/PHASE0-OWNER-CHECKLIST.mdanddocs/STATUS.md, which only exist once this PR merges.Addressed:
447d4b1removes the committed__pycache__/*.pycand ignores Python bytecode; the README links todocs/PHASE0-OWNER-CHECKLIST.mdanddocs/STATUS.mdresolve once this merges. gitleaks over full history: no leaks. Squash-merging so the bytecode never reachesdevelophistory.🤖 Generated with Claude Code