diff --git a/docs/changes/netkingdom-target-2026-10-18/cust80-completion-r4/completion-audit.json b/docs/changes/netkingdom-target-2026-10-18/cust80-completion-r4/completion-audit.json new file mode 100644 index 0000000..ec78440 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/cust80-completion-r4/completion-audit.json @@ -0,0 +1,227 @@ +{ + "objective": "Finish what you found missing and finish CUST-WP-0080", + "status": "active", + "observed_at": "2026-10-18T01:51:53.783602+00:00", + "full_scope_baseline": { + "path": "docs/changes/netkingdom-target-2026-10-18/cust80-completion-r1/completion-audit.json", + "sha256": "sha256:9bdee9a790db598956fe35d5954137bb4582c9f3810d2392f7ef3236c369b065", + "all_requirements_retained": true + }, + "normative_architecture": { + "path": "docs/netkingdom-target-security-architecture.md", + "sha256": "ec2079be18c7cdb8a1e4fdbd22131c08c83721d7a1bf18b4802bc5e229520f35" + }, + "acceptance_gates": [ + { + "id": "TSA-01", + "required_outcome": "Every inventoried privileged PEP authenticates its decision", + "required_evidence": "PEP denominator; admitted signer/channel; forged, altered, wrong-key/pin, unsigned/downgrade, expired and rotation refusals. Start SECRETS-WP-0014.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-02", + "required_outcome": "Exact approval produces one bounded protected operation", + "required_evidence": "SECRETS-WP-0013/RPF-WP-0035-T12 native effect; Approval T01/T05 issuer/tenant provenance; wrong actor/tenant/body, expiry, replay and unavailable refusal.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-03", + "required_outcome": "Crashes preserve uncertainty and cleanup", + "required_evidence": "Actual ledger restart/concurrency, consumed-before-effect failure, target reconciliation, scoped cleanup and revocation evidence; no blind replay.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-04", + "required_outcome": "First reusable browser case is accepted", + "required_evidence": "NK-WP-0048 and named Mason/Warden/KeyCape adapters; independent native structure, sign-in/denial, delivery, revocation and rollback.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-05", + "required_outcome": "Human and browser consumers can use the delivered identity", + "required_evidence": "KEY-WP-0040/U08 real-factor recovery; User Engine journey; NK-WP-0050 and CUST-WP-0080-T05 human/agent Playwright acceptance.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-06", + "required_outcome": "Actual agent and runner authority is bounded", + "required_evidence": "CUST-WP-0073/GLAS-WP-0012 admitted runtime read-denial/tool-effect proof; Forge/Platform per-job credential and repository-workflow abuse tests.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-07", + "required_outcome": "Credential lifecycle is inventoried and simplified", + "required_evidence": "Current owner/consumer denominator, unknown/orphan findings, native-identity elimination candidates; sampled replacement/adoption/old-refusal and deletion semantics. G04/G09/G12.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-08", + "required_outcome": "Recovery is independent and measured", + "required_evidence": "NK-WP-0049 plus platform/root owners; isolated recovery with ordinary issuer unavailable, restore/reconnect/audit continuity and measured RTO/RPO. G08/G11.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-09", + "required_outcome": "Security effects have independently reconciled evidence", + "required_evidence": "Admitted source/class inventory, atomic outbox, receiver readback, lag/duplicate/boundary accounting and missing-class findings. Approval/Audit/Tenant/Secrets owners.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-10", + "required_outcome": "Source and delivery controls cover declared entry paths", + "required_evidence": "FORGE-WP-0006/RMGR-WP-0021: denominator, SSH/HTTP/UI/new repo/import/mirror coverage, required checks/provenance, private synthetic finding through resolution.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-11", + "required_outcome": "Discovery remains accurate", + "required_evidence": "All reviewed IDs exactly once, owner/index parity, usable source links, current limits/version aliases, drift checks and package-family disposition. Reuse/Repo Manager/provider owners.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-12", + "required_outcome": "Improvement operates without repeated manual polling", + "required_evidence": "CUST-WP-0079/Activity accepted schedule, unassisted firing, checkpoint and changed-state notifications; adopted cadence runtime/receiver receipts; owner change and native remeasurement.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-13", + "required_outcome": "Shared adapters reduce per-consumer implementation", + "required_evidence": "Producer-format cross-language fixtures, two real consumers, version compatibility, denied-path parity, upgrades and rollback; no second PDP.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-14", + "required_outcome": "Minimal profiles are independently reusable", + "required_evidence": "Version/provider lock, dependency and optional-provider declaration, provider adapters, second clean installation, complete positive/negative journeys and upgrade/recovery instructions.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-15", + "required_outcome": "Optimization improves measured operations", + "required_evidence": "Baseline and repeat measurements of onboarding time/touches, p50/p95 decision/effect latency, scanner throughput/failure, resources/headroom and owner effort with unchanged control coverage.", + "status": "incomplete", + "completion_evidence": [] + }, + { + "id": "TSA-16", + "required_outcome": "Governance and exceptions remain truthful", + "required_evidence": "Existing SEC-DSM ledger and close-down controls reconciled to exact evidence; real authority for changes, explicit residual promotions, preserved deferrals and no invented owner assent.", + "status": "incomplete", + "completion_evidence": [] + } + ], + "sec_dsm_requirements": { + "path": "docs/evidence/2026-10-12-netkingdom-security/sec-dsm-requirement-ledger.json", + "sha256": "sha256:ad81f7186ada04aa1cc410932fc8669d69dc2232f3c0be00657b17f3aa3a8184", + "count": 116, + "status_counts": { + "unverified": 84, + "incomplete": 12, + "reference": 20 + }, + "closure_status_unchanged": true + }, + "gap_ids": [ + "G01", + "G02", + "G03", + "G04", + "G05", + "G06", + "G07", + "G08", + "G09", + "G10", + "G11", + "G12", + "G13" + ], + "separate_required_deliverables": [ + "G01-G13 current closure evidence or already applicable approved exceptions", + "SEC-DSM requirement ledger reconciled with exact evidence", + "T05 real human and agent Playwright acceptance and supported discovery", + "Second real consumer and clean installation proves reuse", + "Residual handoffs before workplan finish; no required work left only in prose" + ], + "source_progress": { + "packet": "docs/changes/netkingdom-target-2026-10-18/forgejo-postgres-source-r1", + "producer_source_commit": "git:ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3", + "consumer_owner_commit": "git:308e83fcfbf9ec98dbe6b0286cbe8ec7be8899b3", + "consumer_paths": 40, + "patch_sha256": "sha256:feb1079399a43989742ae0a946c1ada50113d1821e6541f97f0b5592ee1fc20c", + "postgreSQL_test_subtest_passes": 37, + "database_schema_profiles": [ + "default", + "explicit-public", + "named" + ], + "separate_client_crash_recovery": true, + "actual_database_server_restart_recovery": true, + "controlled_missing_lock_and_private_marker_counterexamples": true, + "following_query_hook_context_panic_corrected": true, + "selected_composed_passes_including_subtests": 94, + "source_skips": 0, + "release": { + "frontend_and_static_backend": "passed", + "release_version": "11.0.3+cust80.feb10793", + "binary_sha256": "sha256:92efb26614d3fca22c400920d92c4cd022e32ab86c1f562f60699ab5f750cd28", + "embedded_assets_exact": 2234, + "actual_release_CLI_checks": 48, + "exact_local_OCI_exports": 2, + "manifest": "sha256:9d127b0a75d3d53b1598e7f380dce29f23f42c2e69056fdfc9a42122d98c41c7", + "native_admission": false + }, + "independent_owner_assent": false, + "production_database": false, + "native_gates_closed": 0 + }, + "current_work": "Independent issuer/profile review and admission of the issuer-owned online token-status extension; current authenticated operation/inverse and exact effect/approval/cleanup integration in existing NK48/Apps owners; exclusion of every release writer; private journal custody/retention; ongoing HTTP/API/Git/SSH decisions; all sixteen gates, G01-G13 and 116 ledger entries retained.", + "workplan_finished": false, + "goal_complete": false, + "preceding_audit": { + "path": "docs/changes/netkingdom-target-2026-10-18/cust80-completion-r3/completion-audit.json", + "sha256": "sha256:6df13fcbda73baed5333f6344208939676cbee6fa9482a3e6de908abd17aa070" + }, + "issuer_source_progress": { + "packet": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1", + "base_revision": "git:d803e9282a47b49daf930fb905312fa5104c0ace", + "scope": "proposed opt-in extension in the existing issuer", + "source_delta_paths": 18, + "complete_race_passes_including_subtests": 659, + "existing_native_external_skips": 6, + "new_token_status_passes_including_subtests": 43, + "actual_built_server_process_checks": 26, + "actual_process_restart": true, + "actual_handler_HTTPS_PKCE_status_revocation": true, + "controlled_revocation_guard_omission_failed_checks": 7, + "independent_owner_assent": false, + "native_admission": false, + "gates_closed": 0, + "owner_identifier_reconciliation": "existing exact historical reference restored using official consistency injection helper; new canonical records assigned by normal Repo Manager; no replacement existing UUID", + "consumer_owner_commit": "git:67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "owner_primary_projection_verified": true, + "owner_workplan": "KEY-WP-0042", + "owner_workplan_status": "proposed", + "owner_tasks": { + "T01": "progress", + "T02": "done", + "T03": "todo" + }, + "source_task_complete_only": true + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/cust80-token-status-publication-r1/first-commit-refusal.json b/docs/changes/netkingdom-target-2026-10-18/cust80-token-status-publication-r1/first-commit-refusal.json new file mode 100644 index 0000000..ed65dc1 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/cust80-token-status-publication-r1/first-commit-refusal.json @@ -0,0 +1,301 @@ +{ + "exit_code": 1, + "commit": "83078392c076acd1e30425efd81115910f97f006", + "base": "83078392c076acd1e30425efd81115910f97f006", + "files": [ + { + "path": "docs/changes/netkingdom-target-2026-10-18/cust80-completion-r4/completion-audit.json", + "sha256": "8a4e3e61f3639a31d9d5df7803d3cb420eb391b191894e209d62b9e3100d5127" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/README.md", + "sha256": "48a160d8000c3473a574f6ae8572a736584d97c3369e557dc73a95d245dfd7d2" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/apply_owner.py", + "sha256": "82f027e306b0217c5260eabd426161702732c18cb108e80874dececf5d5b373b" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/baseline.json", + "sha256": "fd1bb4074032d34706297723b499ff103be02912d9a08957521eb28fd0ae8114" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.json", + "sha256": "a1feec82fe8cc84c60e545071143acc354dbd5612e823a23884af3c9ff83d858" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.jsonl", + "sha256": "8047cd4f4fbd2e1ef04112f119ac2d80a75673a9c78fdbabb36a0ef876e94bef" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-fixture-receipt.json", + "sha256": "61d2243797160e73e2a9fffe7747857dc84694c7653920c3c505eff4123432a9" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary_fixture.py", + "sha256": "c50b7812a2145de26637ed308d8994283fa96da38b1df767e4c703afa1a09d5c" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.json", + "sha256": "46fc022cc329c20696412a247c107cbe42a75c8d24b9662a8012ee7101c5adf2" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.jsonl", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/committed-source-equivalence.json", + "sha256": "f51bd2e5e90c81d403069a0130cae1d732e3d2c6f1f1ea883b1166c98167377e" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/controlled-revocation-omission.json", + "sha256": "1134048b198312bc75e40ad256ecd68145853dc9a7db013e8f813c471c215497" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.json", + "sha256": "47e230347c930b01dae9e172b0752ab3d096dab81fb02781f8babfbce20305e2" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.jsonl", + "sha256": "a532f528ee71da2cac50a3c488fbf6d94d2d587b6240f41f335a000a5dd7b30b" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.json", + "sha256": "b2792cd084fe03ac3322fb4266caee66480b259f1b8210427c2ca76a8c5bbe09" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.jsonl", + "sha256": "014a3e1d5747b44b37b51ab2a04731813821ba305404da68d6c2edf2539a7f84" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/freeze_candidate.py", + "sha256": "4b3fdad177292a1fde2d02fd74962130f905ec31bab8f936554449606c5ce17e" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.json", + "sha256": "9afdcf762abaf253e3c44ed250188923e71649f7dc58a680c11a444447e47ff0" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.jsonl", + "sha256": "55754790c7bb6839ca607956f64d434bcd05af0dbd4a8570f60c4512bf5e536d" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-source-inventory.json", + "sha256": "31dad565976de6ba7ed447c4a69b96c86d4c2d9e11033e30d9bc15f93c3399bb" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-assignment.json", + "sha256": "f0a4b3e160951f7e49aa01c6626a52830af5740e8697a416537df5a36494d59a" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preflight-refusal.json", + "sha256": "0867d02dbc4751c3642627ffde364a87b2a530283221d7e70eea55d44c8c0ce2" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preview.json", + "sha256": "53b35217e4ad315aabfe9d57e04459e41a3498032a486f810ea62544c7ca1250" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-application.json", + "sha256": "61d748eb7012346f7a72ed688e925d3bf0f86bf538a601e199a0e9196b63a038" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/before.json", + "sha256": "b2b460e0064e040d86dff2cba55e8d496f2f298908980c03f9215fbdd52f6396" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/consistency-result-summary.json", + "sha256": "e7f4aa5832078e5ede03532555b2045728885775ecf364629a3866da6e2ebb1f" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding-restoration-proposal.json", + "sha256": "8a2ef6e3e97c9143045feabf962aa61fe46f1b886ed0b854e1725f8bad7c2611" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding.json", + "sha256": "fb1abb81f5b20a2b20cdad349845e6603728e262b8dedcd784845a8740a98627" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-generated-cleanup.json", + "sha256": "e624fc13d4097733a0afd87c70b3bed17ce6dc853f5719574ec33961d62be7e8" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.json", + "sha256": "fed55936ef8e85acb1f17ae2a014ef9021da87168777650d38b9cbb0053544a8" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.stderr.txt", + "sha256": "f4a6aa1609a13600a0b97cbb08bbc03b3e247b619ff37912d37f7e109ce798ba" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.json", + "sha256": "4d57eb5ffe84416571b4303e72a56bbbe5abb7b94d164e49c94afc3a74adbbf6" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.stderr.txt", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-commit.json", + "sha256": "c0c30cd31ecc5b4a1fffd95db1c1a8e5581ec9c0fc29354fe02d766b5e3af40c" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-inputs.json", + "sha256": "c6256e6c8601e5c64ac99b23799158835a059c987ff896febaacace346d9b310" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-readback.json", + "sha256": "80fb941c71831045de82d7aa71ea73859503d0cb64d9bd2c335b2bc3d9b9bbb4" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-source.patch", + "sha256": "e3d3bc375a8a0a49eff434483050ae338935391b5b361e64f0880ed992f4c85c" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/SCOPE.md", + "sha256": "a80e80ba2e5279ba50232e6b3f57a5bdd14866e584d2d055dfb09e98a4c24ce0" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/online-token-status.md", + "sha256": "f5e41b47500b15c80b2da6cbe37a9e64bcbde1c072750934cf74464a19488925" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/operations.md", + "sha256": "3659362e3c7f8720e52cc8dc7c67a6295ac26462f383c68c06c120fac6e6e472" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/cmd/keycape/main.go", + "sha256": "2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/config.go", + "sha256": "19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/token_status_test.go", + "sha256": "b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/validate.go", + "sha256": "2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/discovery.go", + "sha256": "00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token.go", + "sha256": "304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status.go", + "sha256": "d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_discovery_test.go", + "sha256": "27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_http.go", + "sha256": "a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_internal_test.go", + "sha256": "3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_test.go", + "sha256": "e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_transport_test.go", + "sha256": "3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/userinfo.go", + "sha256": "f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/ADHOC-2026-09-27.md", + "sha256": "f6220f17fc5b6e7300d0a55ac29f78f385ad89db276ccaa21693428fffa5919b" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/KEY-WP-0042-online-access-token-status.md", + "sha256": "6d7ff6dea31b251e261195fff97a2061d56579b5786629d95014f6d1cbe0f55e" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal-manifest.json", + "sha256": "c1a0420b5a497b8d6d913c0dd43f61eb164b832af179d0a7f7ceb457128802a1" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal.patch", + "sha256": "926f36443c36c6568ba4fb5f7db26697bcab01f8ce0beaa57ba227329f156a13" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/SCOPE.md", + "sha256": "20fbadf3e33339bca4b541cdfa33a0de00095c968d51006dc4d0a46735cc834f" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/docs/operations.md", + "sha256": "e68ec32073b7731214308ccd2fceb522d934b00f1d76e6d438e2870030497cfc" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/cmd/keycape/main.go", + "sha256": "ff1ee4f5d2eb86a1e4184374d5244e24704d9d5fd780e173062af07112344afd" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/config.go", + "sha256": "afd465acd9b6879493b888acc6fc54b9a71e032892701a81f1072986d2c30ac7" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/validate.go", + "sha256": "8189ee82e7df74a2a4a1f53e213b1be0a5c737fd76d8f283f7ec9f8ed38c3a65" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/discovery.go", + "sha256": "5ff9ddd9e2f4304b562e116100ca31fe7eabf8315d7d73998d3e6c5bea53956d" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/token.go", + "sha256": "8422cc9dbd39c88dfe835b43f6262cb120bc0d2e5b08898a151167b7555368d3" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/userinfo.go", + "sha256": "bbd5fa9ebd0726371c90d6938536c1ce5fcfacbbe853a80833e3587fc2115a0b" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/workplans/ADHOC-2026-09-27.md", + "sha256": "2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/restore_owner_generated_brief.py", + "sha256": "d3e84fd323cb8c3283d1bb09108cc3bbf8cbc46f504e3bebad1a61d04d5a3dc6" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/run_candidate.py", + "sha256": "df249e1946a3661b47bf4d8515e4faf8d0cdd91d0ebbda2304630aeedafdd214" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-check-equivalence.json", + "sha256": "ad214d41e3ad926eff17784fd2e13ce1b6ca2a30e3b6e5d5efcdd93b5d53ce63" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-receipt.json", + "sha256": "51953a52a52f314ebbd1b00b4ba15ef7eb8845a7e0b000c5b1cfb2b0db0a209f" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-request.json", + "sha256": "c9f3857a28a631899d31493d7d75f7cba5391437f4e9a4e4e1d68383d8dfc53c" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-manifest.json", + "sha256": "a133e0de7bbdaf5cd5a519ddaccd3d314b47da6a3bbd9a883162b9238dda7bea" + }, + { + "path": "workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md", + "sha256": "b78a7089191c0f90de6fdd2ee7e4f901b567da0545a4fc2facad9d8c4b1be4e4" + } + ], + "stdout": "", + "stderr": "{\"blocked_blob_ids\": [\"1344d4edea99df8f85c0884df2650361471597e8\", \"35e8d505c22c8b192017748124b883e69532deb6\", \"8f80cab62acdff2319be10202ca35388a38943e0\"], \"scanned_blobs\": 72, \"scanner_sha256\": \"88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\", \"scanner_version\": \"8.30.1\", \"scope\": \"changed-staged-blobs\", \"status\": \"blocked\"}\n" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/cust80-token-status-publication-r1/scanner-refusal.json b/docs/changes/netkingdom-target-2026-10-18/cust80-token-status-publication-r1/scanner-refusal.json new file mode 100644 index 0000000..aba0244 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/cust80-token-status-publication-r1/scanner-refusal.json @@ -0,0 +1,19 @@ +{ + "status": "blocked", + "source": "unchanged normal hook", + "rows": [ + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.json", + "oid": "git:35e8d505c22c8b192017748124b883e69532deb6" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.json", + "oid": "git:8f80cab62acdff2319be10202ca35388a38943e0" + }, + { + "path": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-source-inventory.json", + "oid": "git:1344d4edea99df8f85c0884df2650361471597e8" + } + ], + "recovery": "inspect exact finding and prove any public-metadata derivation; do not bypass scanner or alter its rules" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/README.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/README.md new file mode 100644 index 0000000..c5f6bd7 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/README.md @@ -0,0 +1,122 @@ +# Issuer-owned online access-token status source proposal + +This packet prepares the missing issuer facility identified while implementing +current operation/inverse sessions for CUST-WP-0080. KeyCape's existing +KEY-WP-0025 correctly documents that its legacy JWTs remain usable until expiry; +Warden's injected current-session evidence verifier does not implement the +missing producer. The selected extension lives in the existing KeyCape issuer. +It introduces no Forgejo-local token registry, broker, PDP or approval authority. + +The original base is KeyCape `d803e928`. `owner-source.patch` and +`source-manifest.json` retain the exact 18-path source/publication delta, and all postimages +replay from that published source archive with fuzz zero. Unrelated source is +unchanged. Independent issuer/profile/consumer assent and native activation +remain open. See the owner publication/consistency receipts for subsequent +metadata reconciliation and the published source revision; a local source +proposal alone is not an owner-reviewed runtime capability. + +## Implemented source behavior + +An explicit `tokenStatus.enabled` profile connects one issuer-owned bounded +hash-only store to actual human/service issuance, UserInfo, RFC 7662 +introspection, RFC 7009 revocation, configuration validation and discovery. +Access tokens gain a unique random jti; ID-token claims and the disabled legacy +profile are preserved. A later grant cannot remint or reactivate a revoked token. +Current registration commitments include the omitted private-secret digest and +internal token lifetime. Introspection authenticates the registered issuing +confidential client; public clients can revoke their own bearer but cannot read +token status. There is no implicit cross-client resource-server delegation. + +Online checks reject unknown/revoked/expired tokens, registration drift, missing +or disabled/suspended humans and unavailable directory lookup. They recheck +status after lookup so a delayed query cannot resurrect revoked authority. +Unknown and other-client tokens disclose no existence information. Requests +and private cache behavior are bounded and reject ambiguity without echoing +values. Live records/tombstones are never evicted to make room for issuance. + +The profile retains the issuer's supported single-instance topology: process +restart loses online status and refuses every earlier token until fresh +authentication. Offline consumers keep their existing JWT lifetime. The full +tradeoff, HTTP/HTTPS topology, capacity/availability limits and rollout contract +are explicit in `postimages/docs/online-token-status.md` and the proposed +`postimages/workplans/KEY-WP-0042-online-access-token-status.md`. + +## Verification + +The initial focused race run passed 40 test/subtest results. The final complete +actual source race suite passed **659**, with six existing native/external skips; +all 43 new token-status test/subtest results passed without skips. These include +an actual HTTPS human PKCE/introspection/revocation/UserInfo flow, both grant +types, public/confidential isolation, capacity/expiry/tombstones, changed current +registration/account disposition, malformed/oversize/ambiguous input, concurrent +store use and revocation during both UserInfo and introspection directory lookup. +The initial focused receipt is a preceding source revision, not another final +suite success. The six skipped tests are listed in the complete receipt. + +The standalone server builds. Its actual main/router/config/grant/store paths +pass **26** isolated process-level checks, including real revocation, predecessor +refusal after an actual process restart, fresh issuance, capacity refusal and +disabled-profile discovery/routes/claims. This process fixture uses local HTTP +inside a private network namespace; the separate actual-handler flow uses HTTPS. +Neither is production ingress, a real user session, owner custody or an admitted +deployment. Synthetic private keys, client credentials and JWTs stay in private +fixture memory/tmpfs; actual daemon logs contain none of them. All owned daemon +processes exit normally; the fixture tmpfs disappears with its sandbox. + +An isolated control omitting only `entry.revoked` from the online predicate fails +seven intended checks, including real HTTPS and delayed-directory paths. Its 36 +passing checks are controls, not a successful suite. Canonical candidate source +is not weakened. `controlled-revocation-omission.json` retains the counterexample. +The full race source, build source and actual-process source match exactly; +`source-check-equivalence.json` records the standalone binary digest. + +`run_candidate.py` exposes `focused`, `full`, `build` and `binary` modes. It uses +an empty HOME, allowlisted environment, read-only public module/toolchain mounts +and private PID/network namespaces. No host credentials or real backend are +available. Receipts/logs refuse overwrite. `freeze_candidate.py` verifies exact +base/replay; the controlled source's sole omitted predicate is explicitly retained. + +## Publication and remaining work + +The first ordinary identifier preview refused an unrelated completed historical +task with a missing file UUID. The existing Hub already has an exact matching +task/binding. The normal consistency audit deliberately skips creation in closed workstreams. +After exact parent/record/title/status/description comparison, its existing +block-injection helper restores that already-recorded UUID in the reviewable +candidate. Repo Manager then assigns the four new canonical proposal records. +No replacement identity or historical status change occurs. The refusal and +existing binding remain in `identifier-preflight-refusal.json` and +`owner-consistency/`. This is separate from runtime source verification. + +The new owner workplan is an explicit **proposed extension**, not a claim that +the existing expiry-only scope already supplied online status. Its source task +is complete; independent issuer/profile review and native lifecycle admission +remain open. Consumer exact-operation/inverse binding and revocation on every +exit stay in existing NK48-T34/T42/T43 and RAPPS16-T08/T09. This issuer facility +does not implement their descriptors, approvals, effects or writer exclusion. + +All sixteen architecture gates, G01–G13 and 116 SEC-DSM ledger entries remain in +the full goal. No native gate is closed by this packet. Required ongoing +HTTP/API/Git/SSH authorization, private journal custody/retention, release-wide +writer exclusion, exact package/type/trust admission and independent owner/native +evidence remain open. + +KeyCape source commit `67a0869c` contains the 17-path feature and one historical +reference restoration. Normal source commit hooks passed; primary projection +verification is retained by `owner-publication/`. The preceding 17-path proposal +patch/manifest remain separate, before historical-reference restoration and +normal deterministic UUID assignment. + +Final primary readback verifies KEY-WP-0042 as proposed, T01 progress, T02 done +(source only), and T03 todo, with all three exact source UUID/status bindings. +The existing historical task UUID/status is unchanged. The tool-generated +KeyCape brief/index is restored to its original state after publication; its +tracked worktree is clean. This does not change the Custodian task/gate counts. + +The unchanged repository hook initially refused three untyped public source +inventories. Every one of their 345 digests was checked against the exact +positive/control source and published with an explicit `sha256:` type. Removing +that type and serializing the retained JSON restores each original receipt byte +for byte; `typed-source-digest-derivation.json` retains original/published hashes. +No runtime/test code, private value or scanner rule changed. The first refusal +is retained by the root publication receipt. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/apply_owner.py b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/apply_owner.py new file mode 100644 index 0000000..dc956f5 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/apply_owner.py @@ -0,0 +1,46 @@ +"""Apply exactly the verified issuer source and existing-binding restoration.""" +from pathlib import Path +import hashlib +import json +import subprocess +import urllib.request + +HERE = Path(__file__).resolve().parent +ROOT = Path('/home/worsch/key-cape') +manifest = json.loads((HERE / 'source-manifest.json').read_text()) +sha = lambda raw: 'sha256:' + hashlib.sha256(raw).hexdigest() +def git(*args): + return subprocess.check_output(['git', '-C', str(ROOT), *args], text=True).strip() +base = manifest['base_revision'].removeprefix('git:') +assert git('rev-parse', 'HEAD') == base +assert git('ls-remote', 'origin', 'refs/heads/main').split()[0] == base +assert set(git('diff', '--name-only', 'HEAD').splitlines()) <= {'.custodian-brief.md', 'WORK-RECORDS.md'} +restoration = json.loads((HERE / 'owner-consistency/existing-binding-restoration-proposal.json').read_text()) +with urllib.request.urlopen('http://127.0.0.1:8000/tasks/' + restoration['existing_hub_task_id'], timeout=20) as response: + task = json.load(response) +assert task['record_id'] == restoration['task_record_id'] and task['workplan_id'] == restoration['parent'] and task['status'] == 'done' +paths = {row['path'] for row in manifest['files']} +protected = {name: sha((ROOT / name).read_bytes()) for name in git('ls-files').splitlines() + if name not in paths and (ROOT / name).is_file()} +original_index = git('ls-files', '--stage', '--', '.custodian-brief.md', 'WORK-RECORDS.md') +for row in manifest['files']: + file = ROOT / row['path'] + assert (not file.exists()) if row['before_sha256'] is None else sha(file.read_bytes()) == row['before_sha256'], row['path'] + assert sha((HERE / 'postimages' / row['path']).read_bytes()) == row['sha256'], row['path'] +for row in manifest['files']: + target = ROOT / row['path'] + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes((HERE / 'postimages' / row['path']).read_bytes()) +assert all(sha((ROOT / name).read_bytes()) == digest for name, digest in protected.items()) +assert git('ls-files', '--stage', '--', '.custodian-brief.md', 'WORK-RECORDS.md') == original_index +receipt = {'status': 'source-applied', 'owner': 'key-cape', 'base': 'git:' + base, 'files': len(paths), + 'other_tracked_files_preserved': len(protected), 'existing_binding_restored': restoration['existing_hub_task_id'], + 'new_identity_for_historical_task': False, 'independent_owner_assent': False, 'native_actions': False} +(HERE / 'owner-application.json').write_text(json.dumps(receipt, indent=2) + '\n') +publication = {'owner': 'key-cape', 'base_revision': base, + 'message': 'Add opt-in issuer-owned access-token status and scoped revocation', + 'files': [{'path': row['path'], 'sha256': row['sha256'].removeprefix('sha256:')} for row in manifest['files']]} +owner = HERE / 'owner-publication' +owner.mkdir(exist_ok=False) +(owner / 'publication-inputs.json').write_text(json.dumps(publication, indent=2) + '\n') +print(json.dumps(receipt)) diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/baseline.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/baseline.json new file mode 100644 index 0000000..760db5d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/baseline.json @@ -0,0 +1,8 @@ +{ + "repo": "key-cape", + "commit": "d803e9282a47b49daf930fb905312fa5104c0ace", + "archive_sha256": "sha256:e6170806640398b325cee8cbf9c8b6f02a1ba5baa94022df6446c69b3fca89c2", + "workspace": "/tmp/custodian-keycape-token-status-r1/source", + "status": "isolated-source-proposal", + "live_authority": false +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.json new file mode 100644 index 0000000..c271488 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.json @@ -0,0 +1,203 @@ +{ + "returncode": 0, + "seconds": 0.53, + "argv": [ + "bwrap", + "--unshare-net", + "--unshare-pid", + "--die-with-parent", + "--new-session", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--symlink", + "usr/bin", + "/bin", + "--proc", + "/proc", + "--dev", + "/dev", + "--tmpfs", + "/tmp", + "--bind", + "/tmp/custodian-keycape-token-status-r1", + "/work", + "--ro-bind", + "/home/worsch/go/pkg/mod", + "/modules", + "--ro-bind", + "/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64", + "/toolchain", + "--chdir", + "/work/source/src", + "--clearenv", + "--ro-bind", + "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary_fixture.py", + "/fixture.py", + "--setenv", + "PATH", + "/toolchain/bin:/usr/bin:/bin", + "--setenv", + "HOME", + "/work/home", + "--setenv", + "GOTOOLCHAIN", + "local", + "--setenv", + "GOPROXY", + "off", + "--setenv", + "GOSUMDB", + "off", + "--setenv", + "GOMODCACHE", + "/modules", + "--setenv", + "GOCACHE", + "/work/gocache", + "--setenv", + "GOMAXPROCS", + "4", + "--setenv", + "CGO_ENABLED", + "1", + "--", + "python3", + "/fixture.py" + ], + "source_sha256_at_start": { + "src/go.mod": "sha256:ab299ad4f94feb78e217e2228a4aa161b166460aa28df0c5009451ac1a337456", + "src/Makefile": "sha256:6cdd3aa6e1cccaf4e028278acafe3e6a8d28170de89ff3462984d4f6eb56bd96", + "src/go.sum": "sha256:b10ad54277fb2bd927d3af727fc4dbb9ebe91a038a75be0469d0253ca699226d", + "src/tests/negative/negative_test.go": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f", + "src/tests/conformance/external_test.go": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb", + "src/tests/migration/scenario_c_test.go": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80", + "src/tests/migration/fixtures_test.go": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de", + "src/tests/migration/scenario_b_test.go": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0", + "src/tests/profile/profile_test.go": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6", + "src/internal/domain/assurance_test.go": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755", + "src/internal/domain/conformance_test.go": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1", + "src/internal/domain/repository.go": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011", + "src/internal/domain/auth.go": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee", + "src/internal/domain/model.go": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029", + "src/internal/domain/assurance.go": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7", + "src/internal/domain/mfa.go": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87", + "src/internal/authclient/cli.go": "sha256:376a8e0d1d60825a8b3ef21d371e3fc353c0a34e461712a6899e264278edbe7f", + "src/internal/authclient/client.go": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5", + "src/internal/authclient/verify.go": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25", + "src/internal/authclient/client_test.go": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa", + "src/internal/authclient/verify_test.go": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc", + "src/internal/validator/report.go": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8", + "src/internal/validator/validator_test.go": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276", + "src/internal/validator/validator.go": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f", + "src/internal/config/account_test.go": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d", + "src/internal/config/validate.go": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405", + "src/internal/config/optional_mfa_test.go": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e", + "src/internal/config/config_test.go": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100", + "src/internal/config/token_status_test.go": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5", + "src/internal/config/config.go": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683", + "src/internal/errors/taxonomy.go": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c", + "src/internal/errors/taxonomy_test.go": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45", + "src/internal/jose/jose.go": "sha256:bcfc30d2175735f37e7ea6f58451e122fbdfe02c562805e5a018c863fb677fab", + "src/internal/jose/jose_test.go": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43", + "src/internal/server/policy/policy_test.go": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2", + "src/internal/server/policy/policy.go": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7", + "src/internal/server/errors/enforcement.go": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2", + "src/internal/server/errors/enforcement_test.go": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c", + "src/internal/server/telemetry/emitter.go": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0", + "src/internal/server/telemetry/emitter_test.go": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a", + "src/internal/server/telemetry/events.go": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167", + "src/internal/server/readiness/readiness_test.go": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6", + "src/internal/server/readiness/readiness.go": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073", + "src/internal/server/oidc/discovery_test.go": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0", + "src/internal/server/oidc/tenant_precondition_test.go": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f", + "src/internal/server/oidc/userinfo.go": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f", + "src/internal/server/oidc/account.go": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a", + "src/internal/server/oidc/policy_runtime_test.go": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a", + "src/internal/server/oidc/handoff_test.go": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02", + "src/internal/server/oidc/human_tenant_test.go": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5", + "src/internal/server/oidc/discovery.go": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5", + "src/internal/server/oidc/jwks.go": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60", + "src/internal/server/oidc/login_session.go": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f", + "src/internal/server/oidc/hardening_test.go": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e", + "src/internal/server/oidc/jwks_test.go": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa", + "src/internal/server/oidc/account_test.go": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763", + "src/internal/server/oidc/token_status_discovery_test.go": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031", + "src/internal/server/oidc/principal_type_guard_test.go": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40", + "src/internal/server/oidc/token_status_http.go": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97", + "src/internal/server/oidc/native_optional_test.go": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5", + "src/internal/server/oidc/authorize_test.go": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d", + "src/internal/server/oidc/session.go": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c", + "src/internal/server/oidc/logout.go": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893", + "src/internal/server/oidc/optional_mfa_test.go": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529", + "src/internal/server/oidc/token.go": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f", + "src/internal/server/oidc/token_status.go": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3", + "src/internal/server/oidc/userinfo_test.go": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80", + "src/internal/server/oidc/signin_diagnosis_test.go": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e", + "src/internal/server/oidc/policy_isolation_test.go": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077", + "src/internal/server/oidc/token_test.go": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33", + "src/internal/server/oidc/audience_test.go": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3", + "src/internal/server/oidc/token_status_transport_test.go": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347", + "src/internal/server/oidc/tenant_test.go": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713", + "src/internal/server/oidc/assurance_time_test.go": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a", + "src/internal/server/oidc/signin_diagnosis.go": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f", + "src/internal/server/oidc/authorize.go": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e", + "src/internal/server/oidc/handoff.go": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4", + "src/internal/server/oidc/token_status_internal_test.go": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53", + "src/internal/server/oidc/token_status_test.go": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90", + "src/internal/migration/lldapexport/exporter.go": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103", + "src/internal/migration/lldapexport/exporter_test.go": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2", + "src/internal/migration/tokeycloak/transformer_test.go": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b", + "src/internal/migration/tokeycloak/transformer.go": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa", + "src/internal/migration/toldap/generator.go": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46", + "src/internal/migration/toldap/generator_test.go": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc", + "src/internal/adapters/tenantengine/adapter.go": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3", + "src/internal/adapters/tenantengine/adapter_test.go": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0", + "src/internal/adapters/lldap/adapter.go": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef", + "src/internal/adapters/lldap/railiance_admin_test.go": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f", + "src/internal/adapters/lldap/adapter_test.go": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081", + "src/internal/adapters/lldap/config_internal_test.go": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68", + "src/internal/adapters/lldap/config.go": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b", + "src/internal/adapters/privacyidea/credential_file_test.go": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6", + "src/internal/adapters/privacyidea/read_scope_test.go": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3", + "src/internal/adapters/privacyidea/adapter.go": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904", + "src/internal/adapters/privacyidea/adapter_test.go": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349", + "src/internal/adapters/privacyidea/optional_mfa_test.go": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65", + "src/internal/adapters/privacyidea/enrollment_state_test.go": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377", + "src/internal/adapters/privacyidea/config.go": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021", + "src/internal/adapters/authelia/verify.go": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4", + "src/internal/adapters/authelia/adapter.go": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45", + "src/internal/adapters/authelia/adapter_test.go": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93", + "src/internal/adapters/authelia/issuer_probe.go": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb", + "src/internal/adapters/authelia/issuer_probe_test.go": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec", + "src/internal/adapters/authelia/config.go": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55", + "src/internal/adapters/authelia/verify_test.go": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511", + "src/cmd/keycape-to-keycloak/main.go": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7", + "src/cmd/lldap-to-ldap/main.go": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e", + "src/cmd/keycape/clients_test.go": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da", + "src/cmd/keycape/checkconfig.go": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2", + "src/cmd/keycape/main.go": "sha256:2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d", + "src/cmd/keycape/checkconfig_test.go": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66", + "src/cmd/validator/main.go": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f", + "src/cmd/lldap-export/main_test.go": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2", + "src/cmd/lldap-export/main.go": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75" + }, + "counts_including_subtests": { + "pass": 0, + "fail": 0, + "skip": 0 + }, + "failures": [], + "skips": [], + "source": "/tmp/custodian-keycape-token-status-r1/source", + "native_acceptance": false, + "credentials_accessed": false, + "network": "private empty namespace; fixture loopback only", + "home": "new empty sandbox HOME" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.jsonl b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.jsonl new file mode 100644 index 0000000..abba8d8 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-attempt1.jsonl @@ -0,0 +1 @@ +{"status": "passed", "checks": 26, "all_owned_processes_terminal": true} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-fixture-receipt.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-fixture-receipt.json new file mode 100644 index 0000000..6809c81 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary-fixture-receipt.json @@ -0,0 +1,115 @@ +{ + "binary_sha256": "sha256:bc1369cf122133642180a9fa3cf92b93e0332f639c4e218d5041af959c77c92b", + "checks": [ + { + "check": "enabled_discovery", + "passed": true + }, + { + "check": "actual_service_grant", + "passed": true + }, + { + "check": "distinct_service_grants", + "passed": true + }, + { + "check": "introspection_private_cache_control", + "passed": true + }, + { + "check": "current_service_metadata", + "passed": true + }, + { + "check": "capacity_refuses_without_token", + "passed": true + }, + { + "check": "actual_revocation", + "passed": true + }, + { + "check": "introspection_private_cache_control", + "passed": true + }, + { + "check": "revoked_token_inactive", + "passed": true + }, + { + "check": "introspection_private_cache_control", + "passed": true + }, + { + "check": "other_grant_still_active", + "passed": true + }, + { + "check": "tombstone_not_evicted", + "passed": true + }, + { + "check": "unauthenticated_introspection_refused", + "passed": true + }, + { + "check": "graceful_process_exit", + "passed": true + }, + { + "check": "introspection_private_cache_control", + "passed": true + }, + { + "check": "actual_process_restart_refuses_predecessor", + "passed": true + }, + { + "check": "introspection_private_cache_control", + "passed": true + }, + { + "check": "fresh_post_restart_grant", + "passed": true + }, + { + "check": "introspection_private_cache_control", + "passed": true + }, + { + "check": "new_grant_does_not_resurrect_predecessor", + "passed": true + }, + { + "check": "graceful_process_exit", + "passed": true + }, + { + "check": "disabled_discovery_omits_endpoints", + "passed": true + }, + { + "check": "disabled_router_has_no_endpoint", + "passed": true + }, + { + "check": "legacy_service_grant_has_no_new_claim", + "passed": true + }, + { + "check": "graceful_process_exit", + "passed": true + }, + { + "check": "actual_process_diagnostics_omit_private_material", + "passed": true + } + ], + "real_credentials": false, + "native_acceptance": false, + "transport": "isolated fixture HTTP backend; separate handler test proves fixture HTTPS", + "source": "standalone built KeyCape executable with actual main router/config/grants/store", + "status": "passed", + "all_owned_processes_terminal": true +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary_fixture.py b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary_fixture.py new file mode 100644 index 0000000..2d46b60 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/binary_fixture.py @@ -0,0 +1,153 @@ +"""Exercise the built server in the runner's private network with synthetic input.""" +import base64 +import hashlib +import http.client +import json +import os +from pathlib import Path +import secrets +import socket +import subprocess +import tempfile +import time +import urllib.parse + +workspace = Path('/work') +binary = workspace / 'keycape-candidate' +assert binary.is_file() +private = Path(tempfile.mkdtemp(prefix='token-status-private-')) +key = private / 'issuer.pem' +with key.open('xb') as output: + os.chmod(key, 0o600) + subprocess.run(['openssl', 'genpkey', '-algorithm', 'RSA', '-pkeyopt', 'rsa_keygen_bits:2048'], + stdout=output, stderr=subprocess.DEVNULL, check=True) +secret = secrets.token_urlsafe(24) +with socket.socket() as listener: + listener.bind(('127.0.0.1', 0)) + port = listener.getsockname()[1] +cfg = {'issuer': 'https://issuer.fixture.invalid', 'port': port, 'tokenLifetime': '15m', + 'privateKeyPem': str(key), 'environment': 'dev', 'tokenStatus': {'enabled': True, 'maxEntries': 2}, + 'clients': [{'clientId': 'fixture-client', 'clientType': 'confidential', + 'grantTypes': ['client_credentials'], 'allowedScopes': ['fixture.read'], + 'secretRef': 'env:FIXTURE_ONLY_CLIENT_SECRET', 'serviceSubject': 'service:fixture', + 'tenant': 'tenant:fixture', 'audience': 'resource:fixture', 'tokenLifetime': '1m'}]} +config = private / 'config.json' +config.write_text(json.dumps(cfg)) +os.chmod(config, 0o600) +auth = 'Basic ' + base64.b64encode(('fixture-client:' + urllib.parse.quote_plus(secret)).encode()).decode() +tokens, checks, logs, processes = [], [], [], [] + +def request(path, form=None, authenticated=True): + conn = http.client.HTTPConnection('127.0.0.1', port, timeout=3) + headers = {} + if authenticated: + headers['Authorization'] = auth + body = None if form is None else urllib.parse.urlencode(form) + if body is not None: + headers['Content-Type'] = 'application/x-www-form-urlencoded' + try: + conn.request('GET' if form is None else 'POST', path, body, headers) + response = conn.getresponse() + return response.status, dict(response.getheaders()), response.read(65537) + finally: + conn.close() + +def check(label, condition): + checks.append({'check': label, 'passed': bool(condition)}) + if not condition: + raise AssertionError(label) + +def start(label): + log = private / (label + '.log') + output = log.open('xb') + process = subprocess.Popen([str(binary), '--config', str(config)], stdout=output, stderr=subprocess.STDOUT, + env={**os.environ, 'FIXTURE_ONLY_CLIENT_SECRET': secret}) + output.close() + logs.append(log) + processes.append(process) + for _ in range(100): + if process.poll() is not None: + raise AssertionError('fixture process exited before readiness') + try: + if request('/healthz', authenticated=False)[0] == 200: + return process + except OSError: + pass + time.sleep(0.02) + raise AssertionError('fixture startup timed out') + +def stop(process): + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait(timeout=3) + raise AssertionError('fixture required forced termination') + check('graceful_process_exit', process.returncode == 0) + +def issue(): + status, _, body = request('/token', {'grant_type': 'client_credentials'}) + if status != 200: + return status, None + token = json.loads(body)['access_token'] + tokens.append(token) + return status, token + +def introspect(raw): + status, headers, body = request('/introspect', {'token': raw}) + check('introspection_private_cache_control', headers.get('Cache-Control') == 'no-store') + return status, json.loads(body) + +report = {'binary_sha256': 'sha256:' + hashlib.sha256(binary.read_bytes()).hexdigest(), + 'checks': checks, 'real_credentials': False, 'native_acceptance': False, + 'transport': 'isolated fixture HTTP backend; separate handler test proves fixture HTTPS', + 'source': 'standalone built KeyCape executable with actual main router/config/grants/store'} +try: + process = start('enabled') + status, _, body = request('/.well-known/openid-configuration', authenticated=False) + discovery = json.loads(body) + check('enabled_discovery', status == 200 and discovery['introspection_endpoint'].endswith('/introspect') and discovery['revocation_endpoint'].endswith('/revoke')) + status, first = issue() + check('actual_service_grant', status == 200 and bool(first)) + status, second = issue() + check('distinct_service_grants', status == 200 and first != second) + status, metadata = introspect(first) + check('current_service_metadata', status == 200 and metadata.get('active') is True and metadata.get('aud') == 'resource:fixture' and metadata.get('client_id') == 'fixture-client') + check('capacity_refuses_without_token', issue() == (503, None)) + status, _, body = request('/revoke', {'token': first}) + check('actual_revocation', status == 200 and body == b'') + check('revoked_token_inactive', introspect(first)[1] == {'active': False}) + check('other_grant_still_active', introspect(second)[1].get('active') is True) + check('tombstone_not_evicted', issue() == (503, None)) + check('unauthenticated_introspection_refused', request('/introspect', {'token': second}, authenticated=False)[0] == 401) + stop(process) + process = start('restarted') + check('actual_process_restart_refuses_predecessor', introspect(second)[1] == {'active': False}) + status, third = issue() + check('fresh_post_restart_grant', status == 200 and third not in (first, second) and introspect(third)[1].get('active') is True) + check('new_grant_does_not_resurrect_predecessor', introspect(first)[1] == {'active': False}) + stop(process) + cfg['tokenStatus'] = {'enabled': False} + config.write_text(json.dumps(cfg)) + process = start('legacy') + _, _, body = request('/.well-known/openid-configuration', authenticated=False) + discovery = json.loads(body) + check('disabled_discovery_omits_endpoints', 'introspection_endpoint' not in discovery and 'revocation_endpoint' not in discovery) + check('disabled_router_has_no_endpoint', request('/introspect', {'token': second})[0] == 404) + status, fourth = issue() + payload = fourth.split('.')[1] + claims = json.loads(base64.urlsafe_b64decode(payload + '=' * (-len(payload) % 4))) + check('legacy_service_grant_has_no_new_claim', status == 200 and 'jti' not in claims) + stop(process) + combined = b''.join(path.read_bytes() for path in logs) + needles = [secret.encode(), key.read_bytes(), *(token.encode() for token in tokens)] + check('actual_process_diagnostics_omit_private_material', all(value not in combined for value in needles)) + report['status'] = 'passed' +finally: + for process in processes: + if process.poll() is None: + stop(process) + report['all_owned_processes_terminal'] = all(p.poll() is not None for p in processes) + (workspace / 'binary-fixture-receipt.json').write_text(json.dumps(report, indent=2) + '\n') +print(json.dumps({'status': report['status'], 'checks': len(checks), 'all_owned_processes_terminal': report['all_owned_processes_terminal']})) diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.json new file mode 100644 index 0000000..fb3d8ee --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.json @@ -0,0 +1,86 @@ +{ + "returncode": 0, + "seconds": 22.93, + "argv": [ + "bwrap", + "--unshare-net", + "--unshare-pid", + "--die-with-parent", + "--new-session", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--symlink", + "usr/bin", + "/bin", + "--proc", + "/proc", + "--dev", + "/dev", + "--tmpfs", + "/tmp", + "--bind", + "/tmp/custodian-keycape-token-status-r1", + "/work", + "--ro-bind", + "/home/worsch/go/pkg/mod", + "/modules", + "--ro-bind", + "/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64", + "/toolchain", + "--chdir", + "/work/source/src", + "--clearenv", + "--setenv", + "PATH", + "/toolchain/bin:/usr/bin:/bin", + "--setenv", + "HOME", + "/work/home", + "--setenv", + "GOTOOLCHAIN", + "local", + "--setenv", + "GOPROXY", + "off", + "--setenv", + "GOSUMDB", + "off", + "--setenv", + "GOMODCACHE", + "/modules", + "--setenv", + "GOCACHE", + "/work/gocache", + "--setenv", + "GOMAXPROCS", + "4", + "--setenv", + "CGO_ENABLED", + "1", + "--", + "go", + "build", + "-o", + "/work/keycape-candidate", + "./cmd/keycape" + ], + "counts_including_subtests": { + "pass": 0, + "fail": 0, + "skip": 0 + }, + "failures": [], + "skips": [], + "source": "/tmp/custodian-keycape-token-status-r1/source", + "native_acceptance": false, + "credentials_accessed": false, + "network": "private empty namespace; fixture loopback only", + "home": "new empty sandbox HOME" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.jsonl b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/build-attempt1.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/committed-source-equivalence.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/committed-source-equivalence.json new file mode 100644 index 0000000..11da4e0 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/committed-source-equivalence.json @@ -0,0 +1,8 @@ +{ + "commit": "git:67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "source_files": 115, + "committed_runtime_test_and_build_source_exact": true, + "commit_paths": 18, + "exact_frozen_delta": true, + "production_deployed": false +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/controlled-revocation-omission.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/controlled-revocation-omission.json new file mode 100644 index 0000000..05b3d24 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/controlled-revocation-omission.json @@ -0,0 +1,24 @@ +{ + "control_source": "/tmp/custodian-keycape-token-status-control-r1/source", + "changed_only": "src/internal/server/oidc/token_status.go", + "production_file_sha256": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3", + "control_file_sha256": "sha256:895cffaee454023a6e90603b34a4f90796b5a38714129db103d4020380fdf788", + "omitted": "entry.revoked in online active predicate", + "expected_result": "focused revocation tests fail", + "canonical_source_weakened": false, + "actual_result": { + "pass": 36, + "fail": 7, + "skip": 0 + }, + "expected_counterexample_reproduced": true, + "failed_tests": [ + "keycape/internal/server/oidc:TestTokenStatusConcurrentIssueReadAndRevoke", + "keycape/internal/server/oidc:TestTokenStatusActualIssueRevokeAndFreshGrant", + "keycape/internal/server/oidc:TestTokenStatusPublicClientMayRevokeButNotIntrospect", + "keycape/internal/server/oidc:TestTokenStatusServiceGrantIsCurrentAndDistinct", + "keycape/internal/server/oidc:TestTokenStatusRevocationDuringDirectoryLookupRefuses", + "keycape/internal/server/oidc:TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding", + "keycape/internal/server/oidc:TestTokenStatusRevocationDuringUserInfoLookupRefuses" + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.json new file mode 100644 index 0000000..b513f46 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.json @@ -0,0 +1,92 @@ +{ + "returncode": 0, + "seconds": 41.81, + "argv": [ + "bwrap", + "--unshare-net", + "--unshare-pid", + "--die-with-parent", + "--new-session", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--symlink", + "usr/bin", + "/bin", + "--proc", + "/proc", + "--dev", + "/dev", + "--tmpfs", + "/tmp", + "--bind", + "/tmp/custodian-keycape-token-status-r1", + "/work", + "--ro-bind", + "/home/worsch/go/pkg/mod", + "/modules", + "--ro-bind", + "/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64", + "/toolchain", + "--chdir", + "/work/source/src", + "--clearenv", + "--setenv", + "PATH", + "/toolchain/bin:/usr/bin:/bin", + "--setenv", + "HOME", + "/work/home", + "--setenv", + "GOTOOLCHAIN", + "local", + "--setenv", + "GOPROXY", + "off", + "--setenv", + "GOSUMDB", + "off", + "--setenv", + "GOMODCACHE", + "/modules", + "--setenv", + "GOCACHE", + "/work/gocache", + "--setenv", + "GOMAXPROCS", + "4", + "--setenv", + "CGO_ENABLED", + "1", + "--", + "go", + "test", + "-p", + "4", + "-race", + "-json", + "-count=1", + "-run", + "^TestTokenStatus", + "./internal/server/oidc", + "./internal/config" + ], + "counts_including_subtests": { + "pass": 40, + "fail": 0, + "skip": 0 + }, + "failures": [], + "skips": [], + "source": "/tmp/custodian-keycape-token-status-r1/source", + "native_acceptance": false, + "credentials_accessed": false, + "network": "private empty namespace; fixture loopback only", + "home": "new empty sandbox HOME" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.jsonl b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.jsonl new file mode 100644 index 0000000..a8480cb --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt1.jsonl @@ -0,0 +1,168 @@ +{"Time":"2026-10-18T01:44:05.869783474Z","Action":"start","Package":"keycape/internal/server/oidc"} +{"Time":"2026-10-18T01:44:05.870004688Z","Action":"start","Package":"keycape/internal/config"} +{"Time":"2026-10-18T01:44:05.888599214Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS"} +{"Time":"2026-10-18T01:44:05.888664003Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS\n"} +{"Time":"2026-10-18T01:44:05.888786709Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy"} +{"Time":"2026-10-18T01:44:05.888798946Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy\n"} +{"Time":"2026-10-18T01:44:05.888820948Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.888850596Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Elapsed":0} +{"Time":"2026-10-18T01:44:05.888958821Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default"} +{"Time":"2026-10-18T01:44:05.888973348Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default\n"} +{"Time":"2026-10-18T01:44:05.889148206Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.889169423Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Elapsed":0} +{"Time":"2026-10-18T01:44:05.889270584Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded"} +{"Time":"2026-10-18T01:44:05.889293848Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded\n"} +{"Time":"2026-10-18T01:44:05.889433249Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.889459607Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Elapsed":0} +{"Time":"2026-10-18T01:44:05.889469922Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http"} +{"Time":"2026-10-18T01:44:05.889474936Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http\n"} +{"Time":"2026-10-18T01:44:05.889659657Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.889682507Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Elapsed":0} +{"Time":"2026-10-18T01:44:05.889694013Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url"} +{"Time":"2026-10-18T01:44:05.889700776Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url\n"} +{"Time":"2026-10-18T01:44:05.889937963Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.889962165Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Elapsed":0} +{"Time":"2026-10-18T01:44:05.889975601Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query"} +{"Time":"2026-10-18T01:44:05.889987612Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query\n"} +{"Time":"2026-10-18T01:44:05.89015636Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.890183719Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Elapsed":0} +{"Time":"2026-10-18T01:44:05.890294775Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large"} +{"Time":"2026-10-18T01:44:05.890309879Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large\n"} +{"Time":"2026-10-18T01:44:05.890418131Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.890444398Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Elapsed":0} +{"Time":"2026-10-18T01:44:05.89047136Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative"} +{"Time":"2026-10-18T01:44:05.890477879Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative\n"} +{"Time":"2026-10-18T01:44:05.8907124Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.890737883Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Elapsed":0} +{"Time":"2026-10-18T01:44:05.8907513Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity"} +{"Time":"2026-10-18T01:44:05.89075754Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity\n"} +{"Time":"2026-10-18T01:44:05.89097219Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.891008602Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Elapsed":0} +{"Time":"2026-10-18T01:44:05.891021523Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.891053598Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Elapsed":0} +{"Time":"2026-10-18T01:44:05.891065041Z","Action":"output","Package":"keycape/internal/config","Output":"PASS\n"} +{"Time":"2026-10-18T01:44:05.89151663Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones"} +{"Time":"2026-10-18T01:44:05.891557334Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Output":"=== RUN TestTokenStatusExactExpiryCapacityAndTombstones\n"} +{"Time":"2026-10-18T01:44:05.892793338Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Output":"--- PASS: TestTokenStatusExactExpiryCapacityAndTombstones (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.892909156Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Elapsed":0} +{"Time":"2026-10-18T01:44:05.892991853Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke"} +{"Time":"2026-10-18T01:44:05.893000203Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":"=== RUN TestTokenStatusConcurrentIssueReadAndRevoke\n"} +{"Time":"2026-10-18T01:44:05.912013424Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":"--- PASS: TestTokenStatusConcurrentIssueReadAndRevoke (0.02s)\n"} +{"Time":"2026-10-18T01:44:05.912065174Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Elapsed":0.02} +{"Time":"2026-10-18T01:44:05.912536595Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile"} +{"Time":"2026-10-18T01:44:05.912548534Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Output":"=== RUN TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile\n"} +{"Time":"2026-10-18T01:44:05.914026065Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Output":"--- PASS: TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile (0.00s)\n"} +{"Time":"2026-10-18T01:44:05.914051989Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Elapsed":0} +{"Time":"2026-10-18T01:44:05.914139592Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant"} +{"Time":"2026-10-18T01:44:05.914150016Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":"=== RUN TestTokenStatusActualIssueRevokeAndFreshGrant\n"} +{"Time":"2026-10-18T01:44:06.013925253Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":"--- PASS: TestTokenStatusActualIssueRevokeAndFreshGrant (0.10s)\n"} +{"Time":"2026-10-18T01:44:06.013986759Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Elapsed":0.1} +{"Time":"2026-10-18T01:44:06.014002657Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority"} +{"Time":"2026-10-18T01:44:06.014008536Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Output":"=== RUN TestTokenStatusNoCrossClientOrIDTokenAuthority\n"} +{"Time":"2026-10-18T01:44:06.181997808Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Output":"--- PASS: TestTokenStatusNoCrossClientOrIDTokenAuthority (0.17s)\n"} +{"Time":"2026-10-18T01:44:06.182074275Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Elapsed":0.17} +{"Time":"2026-10-18T01:44:06.182087133Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken"} +{"Time":"2026-10-18T01:44:06.182093599Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Output":"=== RUN TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken\n"} +{"Time":"2026-10-18T01:44:06.265364258Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Output":"--- PASS: TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken (0.08s)\n"} +{"Time":"2026-10-18T01:44:06.26545214Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Elapsed":0.08} +{"Time":"2026-10-18T01:44:06.265490129Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse"} +{"Time":"2026-10-18T01:44:06.265501446Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Output":"=== RUN TestTokenStatusRestartAndUnavailableStoreRefuse\n"} +{"Time":"2026-10-18T01:44:06.4996983Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Output":"--- PASS: TestTokenStatusRestartAndUnavailableStoreRefuse (0.23s)\n"} +{"Time":"2026-10-18T01:44:06.499751267Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Elapsed":0.23} +{"Time":"2026-10-18T01:44:06.49977317Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition"} +{"Time":"2026-10-18T01:44:06.499779816Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition\n"} +{"Time":"2026-10-18T01:44:06.499795803Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed"} +{"Time":"2026-10-18T01:44:06.499802106Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed\n"} +{"Time":"2026-10-18T01:44:06.796536412Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed (0.30s)\n"} +{"Time":"2026-10-18T01:44:06.796593212Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Elapsed":0.3} +{"Time":"2026-10-18T01:44:06.796607738Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience"} +{"Time":"2026-10-18T01:44:06.796614934Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience\n"} +{"Time":"2026-10-18T01:44:06.895673725Z","Action":"output","Package":"keycape/internal/config","Output":"ok \tkeycape/internal/config\t1.025s\n"} +{"Time":"2026-10-18T01:44:06.895748009Z","Action":"pass","Package":"keycape/internal/config","Elapsed":1.026} +{"Time":"2026-10-18T01:44:06.914170247Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience (0.12s)\n"} +{"Time":"2026-10-18T01:44:06.914251853Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Elapsed":0.12} +{"Time":"2026-10-18T01:44:06.914263007Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret"} +{"Time":"2026-10-18T01:44:06.914269337Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret\n"} +{"Time":"2026-10-18T01:44:07.087510198Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret (0.17s)\n"} +{"Time":"2026-10-18T01:44:07.087570171Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Elapsed":0.17} +{"Time":"2026-10-18T01:44:07.087586312Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes"} +{"Time":"2026-10-18T01:44:07.087594419Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes\n"} +{"Time":"2026-10-18T01:44:07.411672738Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes (0.32s)\n"} +{"Time":"2026-10-18T01:44:07.411745147Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Elapsed":0.32} +{"Time":"2026-10-18T01:44:07.411764173Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime"} +{"Time":"2026-10-18T01:44:07.411771297Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime\n"} +{"Time":"2026-10-18T01:44:07.452142752Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime (0.04s)\n"} +{"Time":"2026-10-18T01:44:07.452209615Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Elapsed":0.04} +{"Time":"2026-10-18T01:44:07.452224133Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended"} +{"Time":"2026-10-18T01:44:07.452233015Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended\n"} +{"Time":"2026-10-18T01:44:07.561804091Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended (0.11s)\n"} +{"Time":"2026-10-18T01:44:07.561865643Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Elapsed":0.11} +{"Time":"2026-10-18T01:44:07.56187778Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled"} +{"Time":"2026-10-18T01:44:07.561885734Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled\n"} +{"Time":"2026-10-18T01:44:07.648057558Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled (0.09s)\n"} +{"Time":"2026-10-18T01:44:07.648112969Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Elapsed":0.09} +{"Time":"2026-10-18T01:44:07.648126243Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed"} +{"Time":"2026-10-18T01:44:07.648131563Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed\n"} +{"Time":"2026-10-18T01:44:07.731499528Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed (0.08s)\n"} +{"Time":"2026-10-18T01:44:07.731566598Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Elapsed":0.08} +{"Time":"2026-10-18T01:44:07.731578618Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition (1.23s)\n"} +{"Time":"2026-10-18T01:44:07.731591188Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Elapsed":1.23} +{"Time":"2026-10-18T01:44:07.731597635Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect"} +{"Time":"2026-10-18T01:44:07.731603497Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":"=== RUN TestTokenStatusPublicClientMayRevokeButNotIntrospect\n"} +{"Time":"2026-10-18T01:44:07.88603636Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":"--- PASS: TestTokenStatusPublicClientMayRevokeButNotIntrospect (0.15s)\n"} +{"Time":"2026-10-18T01:44:07.886092438Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Elapsed":0.15} +{"Time":"2026-10-18T01:44:07.88610747Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct"} +{"Time":"2026-10-18T01:44:07.886114972Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":"=== RUN TestTokenStatusServiceGrantIsCurrentAndDistinct\n"} +{"Time":"2026-10-18T01:44:07.986961214Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":"--- PASS: TestTokenStatusServiceGrantIsCurrentAndDistinct (0.10s)\n"} +{"Time":"2026-10-18T01:44:07.987021386Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Elapsed":0.1} +{"Time":"2026-10-18T01:44:07.987032442Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests"} +{"Time":"2026-10-18T01:44:07.987038709Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests\n"} +{"Time":"2026-10-18T01:44:07.987050079Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get"} +{"Time":"2026-10-18T01:44:07.987056184Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get\n"} +{"Time":"2026-10-18T01:44:08.036241913Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get (0.05s)\n"} +{"Time":"2026-10-18T01:44:08.036336117Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Elapsed":0.05} +{"Time":"2026-10-18T01:44:08.036368065Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query"} +{"Time":"2026-10-18T01:44:08.036382718Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query\n"} +{"Time":"2026-10-18T01:44:08.199358535Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query (0.16s)\n"} +{"Time":"2026-10-18T01:44:08.199414424Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Elapsed":0.16} +{"Time":"2026-10-18T01:44:08.199426444Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json"} +{"Time":"2026-10-18T01:44:08.199433946Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json\n"} +{"Time":"2026-10-18T01:44:08.222687997Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json (0.02s)\n"} +{"Time":"2026-10-18T01:44:08.222780208Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Elapsed":0.02} +{"Time":"2026-10-18T01:44:08.222809712Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token"} +{"Time":"2026-10-18T01:44:08.222819469Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token\n"} +{"Time":"2026-10-18T01:44:08.362536745Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token (0.14s)\n"} +{"Time":"2026-10-18T01:44:08.36261863Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Elapsed":0.14} +{"Time":"2026-10-18T01:44:08.362639424Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize"} +{"Time":"2026-10-18T01:44:08.362647404Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize\n"} +{"Time":"2026-10-18T01:44:08.490557458Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize (0.13s)\n"} +{"Time":"2026-10-18T01:44:08.490632446Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Elapsed":0.13} +{"Time":"2026-10-18T01:44:08.490646116Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret"} +{"Time":"2026-10-18T01:44:08.490656639Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret\n"} +{"Time":"2026-10-18T01:44:08.604489392Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret (0.11s)\n"} +{"Time":"2026-10-18T01:44:08.604592233Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Elapsed":0.11} +{"Time":"2026-10-18T01:44:08.604613063Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth"} +{"Time":"2026-10-18T01:44:08.604619916Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth\n"} +{"Time":"2026-10-18T01:44:08.767525032Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth (0.16s)\n"} +{"Time":"2026-10-18T01:44:08.767591787Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Elapsed":0.16} +{"Time":"2026-10-18T01:44:08.767600407Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth"} +{"Time":"2026-10-18T01:44:08.767604078Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth\n"} +{"Time":"2026-10-18T01:44:08.831033225Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth (0.06s)\n"} +{"Time":"2026-10-18T01:44:08.831104749Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Elapsed":0.06} +{"Time":"2026-10-18T01:44:08.831125191Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret"} +{"Time":"2026-10-18T01:44:08.831136138Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret\n"} +{"Time":"2026-10-18T01:44:08.93524315Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret (0.10s)\n"} +{"Time":"2026-10-18T01:44:08.935298984Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Elapsed":0.1} +{"Time":"2026-10-18T01:44:08.935312303Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client"} +{"Time":"2026-10-18T01:44:08.935317488Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client\n"} +{"Time":"2026-10-18T01:44:09.102033666Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client (0.17s)\n"} +{"Time":"2026-10-18T01:44:09.10209969Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Elapsed":0.17} +{"Time":"2026-10-18T01:44:09.102112828Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests (1.12s)\n"} +{"Time":"2026-10-18T01:44:09.102124803Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Elapsed":1.12} +{"Time":"2026-10-18T01:44:09.102133099Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses"} +{"Time":"2026-10-18T01:44:09.102140142Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":"=== RUN TestTokenStatusRevocationDuringDirectoryLookupRefuses\n"} +{"Time":"2026-10-18T01:44:09.319280013Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":"--- PASS: TestTokenStatusRevocationDuringDirectoryLookupRefuses (0.22s)\n"} +{"Time":"2026-10-18T01:44:09.319336497Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Elapsed":0.22} +{"Time":"2026-10-18T01:44:09.319352061Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"PASS\n"} +{"Time":"2026-10-18T01:44:10.326829746Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"ok \tkeycape/internal/server/oidc\t4.457s\n"} +{"Time":"2026-10-18T01:44:10.326880134Z","Action":"pass","Package":"keycape/internal/server/oidc","Elapsed":4.457} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.json new file mode 100644 index 0000000..7ff6152 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.json @@ -0,0 +1,220 @@ +{ + "returncode": 1, + "seconds": 7.3, + "argv": [ + "bwrap", + "--unshare-net", + "--unshare-pid", + "--die-with-parent", + "--new-session", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--symlink", + "usr/bin", + "/bin", + "--proc", + "/proc", + "--dev", + "/dev", + "--tmpfs", + "/tmp", + "--bind", + "/tmp/custodian-keycape-token-status-control-r1", + "/work", + "--ro-bind", + "/home/worsch/go/pkg/mod", + "/modules", + "--ro-bind", + "/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64", + "/toolchain", + "--chdir", + "/work/source/src", + "--clearenv", + "--bind", + "/tmp/custodian-keycape-token-status-r1/gocache", + "/work/gocache", + "--setenv", + "PATH", + "/toolchain/bin:/usr/bin:/bin", + "--setenv", + "HOME", + "/work/home", + "--setenv", + "GOTOOLCHAIN", + "local", + "--setenv", + "GOPROXY", + "off", + "--setenv", + "GOSUMDB", + "off", + "--setenv", + "GOMODCACHE", + "/modules", + "--setenv", + "GOCACHE", + "/work/gocache", + "--setenv", + "GOMAXPROCS", + "4", + "--setenv", + "CGO_ENABLED", + "1", + "--", + "go", + "test", + "-p", + "4", + "-race", + "-json", + "-count=1", + "-run", + "^TestTokenStatus", + "./internal/server/oidc", + "./internal/config" + ], + "source_sha256_at_start": { + "src/go.mod": "sha256:ab299ad4f94feb78e217e2228a4aa161b166460aa28df0c5009451ac1a337456", + "src/Makefile": "sha256:6cdd3aa6e1cccaf4e028278acafe3e6a8d28170de89ff3462984d4f6eb56bd96", + "src/go.sum": "sha256:b10ad54277fb2bd927d3af727fc4dbb9ebe91a038a75be0469d0253ca699226d", + "src/tests/negative/negative_test.go": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f", + "src/tests/conformance/external_test.go": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb", + "src/tests/migration/scenario_c_test.go": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80", + "src/tests/migration/fixtures_test.go": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de", + "src/tests/migration/scenario_b_test.go": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0", + "src/tests/profile/profile_test.go": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6", + "src/internal/domain/assurance_test.go": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755", + "src/internal/domain/conformance_test.go": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1", + "src/internal/domain/repository.go": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011", + "src/internal/domain/auth.go": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee", + "src/internal/domain/model.go": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029", + "src/internal/domain/assurance.go": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7", + "src/internal/domain/mfa.go": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87", + "src/internal/authclient/cli.go": "sha256:376a8e0d1d60825a8b3ef21d371e3fc353c0a34e461712a6899e264278edbe7f", + "src/internal/authclient/client.go": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5", + "src/internal/authclient/verify.go": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25", + "src/internal/authclient/client_test.go": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa", + "src/internal/authclient/verify_test.go": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc", + "src/internal/validator/report.go": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8", + "src/internal/validator/validator_test.go": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276", + "src/internal/validator/validator.go": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f", + "src/internal/config/account_test.go": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d", + "src/internal/config/validate.go": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405", + "src/internal/config/optional_mfa_test.go": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e", + "src/internal/config/config_test.go": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100", + "src/internal/config/token_status_test.go": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5", + "src/internal/config/config.go": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683", + "src/internal/errors/taxonomy.go": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c", + "src/internal/errors/taxonomy_test.go": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45", + "src/internal/jose/jose.go": "sha256:bcfc30d2175735f37e7ea6f58451e122fbdfe02c562805e5a018c863fb677fab", + "src/internal/jose/jose_test.go": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43", + "src/internal/server/policy/policy_test.go": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2", + "src/internal/server/policy/policy.go": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7", + "src/internal/server/errors/enforcement.go": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2", + "src/internal/server/errors/enforcement_test.go": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c", + "src/internal/server/telemetry/emitter.go": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0", + "src/internal/server/telemetry/emitter_test.go": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a", + "src/internal/server/telemetry/events.go": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167", + "src/internal/server/readiness/readiness_test.go": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6", + "src/internal/server/readiness/readiness.go": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073", + "src/internal/server/oidc/discovery_test.go": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0", + "src/internal/server/oidc/tenant_precondition_test.go": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f", + "src/internal/server/oidc/userinfo.go": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f", + "src/internal/server/oidc/account.go": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a", + "src/internal/server/oidc/policy_runtime_test.go": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a", + "src/internal/server/oidc/handoff_test.go": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02", + "src/internal/server/oidc/human_tenant_test.go": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5", + "src/internal/server/oidc/discovery.go": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5", + "src/internal/server/oidc/jwks.go": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60", + "src/internal/server/oidc/login_session.go": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f", + "src/internal/server/oidc/hardening_test.go": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e", + "src/internal/server/oidc/jwks_test.go": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa", + "src/internal/server/oidc/account_test.go": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763", + "src/internal/server/oidc/token_status_discovery_test.go": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031", + "src/internal/server/oidc/principal_type_guard_test.go": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40", + "src/internal/server/oidc/token_status_http.go": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97", + "src/internal/server/oidc/native_optional_test.go": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5", + "src/internal/server/oidc/authorize_test.go": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d", + "src/internal/server/oidc/session.go": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c", + "src/internal/server/oidc/logout.go": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893", + "src/internal/server/oidc/optional_mfa_test.go": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529", + "src/internal/server/oidc/token.go": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f", + "src/internal/server/oidc/token_status.go": "sha256:895cffaee454023a6e90603b34a4f90796b5a38714129db103d4020380fdf788", + "src/internal/server/oidc/userinfo_test.go": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80", + "src/internal/server/oidc/signin_diagnosis_test.go": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e", + "src/internal/server/oidc/policy_isolation_test.go": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077", + "src/internal/server/oidc/token_test.go": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33", + "src/internal/server/oidc/audience_test.go": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3", + "src/internal/server/oidc/token_status_transport_test.go": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347", + "src/internal/server/oidc/tenant_test.go": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713", + "src/internal/server/oidc/assurance_time_test.go": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a", + "src/internal/server/oidc/signin_diagnosis.go": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f", + "src/internal/server/oidc/authorize.go": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e", + "src/internal/server/oidc/handoff.go": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4", + "src/internal/server/oidc/token_status_internal_test.go": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53", + "src/internal/server/oidc/token_status_test.go": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90", + "src/internal/migration/lldapexport/exporter.go": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103", + "src/internal/migration/lldapexport/exporter_test.go": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2", + "src/internal/migration/tokeycloak/transformer_test.go": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b", + "src/internal/migration/tokeycloak/transformer.go": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa", + "src/internal/migration/toldap/generator.go": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46", + "src/internal/migration/toldap/generator_test.go": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc", + "src/internal/adapters/tenantengine/adapter.go": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3", + "src/internal/adapters/tenantengine/adapter_test.go": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0", + "src/internal/adapters/lldap/adapter.go": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef", + "src/internal/adapters/lldap/railiance_admin_test.go": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f", + "src/internal/adapters/lldap/adapter_test.go": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081", + "src/internal/adapters/lldap/config_internal_test.go": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68", + "src/internal/adapters/lldap/config.go": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b", + "src/internal/adapters/privacyidea/credential_file_test.go": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6", + "src/internal/adapters/privacyidea/read_scope_test.go": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3", + "src/internal/adapters/privacyidea/adapter.go": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904", + "src/internal/adapters/privacyidea/adapter_test.go": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349", + "src/internal/adapters/privacyidea/optional_mfa_test.go": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65", + "src/internal/adapters/privacyidea/enrollment_state_test.go": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377", + "src/internal/adapters/privacyidea/config.go": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021", + "src/internal/adapters/authelia/verify.go": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4", + "src/internal/adapters/authelia/adapter.go": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45", + "src/internal/adapters/authelia/adapter_test.go": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93", + "src/internal/adapters/authelia/issuer_probe.go": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb", + "src/internal/adapters/authelia/issuer_probe_test.go": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec", + "src/internal/adapters/authelia/config.go": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55", + "src/internal/adapters/authelia/verify_test.go": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511", + "src/cmd/keycape-to-keycloak/main.go": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7", + "src/cmd/lldap-to-ldap/main.go": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e", + "src/cmd/keycape/clients_test.go": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da", + "src/cmd/keycape/checkconfig.go": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2", + "src/cmd/keycape/main.go": "sha256:2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d", + "src/cmd/keycape/checkconfig_test.go": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66", + "src/cmd/validator/main.go": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f", + "src/cmd/lldap-export/main_test.go": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2", + "src/cmd/lldap-export/main.go": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75" + }, + "counts_including_subtests": { + "pass": 36, + "fail": 7, + "skip": 0 + }, + "failures": [ + "keycape/internal/server/oidc:TestTokenStatusConcurrentIssueReadAndRevoke", + "keycape/internal/server/oidc:TestTokenStatusActualIssueRevokeAndFreshGrant", + "keycape/internal/server/oidc:TestTokenStatusPublicClientMayRevokeButNotIntrospect", + "keycape/internal/server/oidc:TestTokenStatusServiceGrantIsCurrentAndDistinct", + "keycape/internal/server/oidc:TestTokenStatusRevocationDuringDirectoryLookupRefuses", + "keycape/internal/server/oidc:TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding", + "keycape/internal/server/oidc:TestTokenStatusRevocationDuringUserInfoLookupRefuses" + ], + "skips": [], + "source": "/tmp/custodian-keycape-token-status-control-r1/source", + "native_acceptance": false, + "credentials_accessed": false, + "network": "private empty namespace; fixture loopback only", + "home": "new empty sandbox HOME" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.jsonl b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.jsonl new file mode 100644 index 0000000..db3ef48 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/focused-attempt2.jsonl @@ -0,0 +1,250 @@ +{"Time":"2026-10-18T01:48:40.055464485Z","Action":"start","Package":"keycape/internal/server/oidc"} +{"Time":"2026-10-18T01:48:40.056054823Z","Action":"start","Package":"keycape/internal/config"} +{"Time":"2026-10-18T01:48:40.075378651Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS"} +{"Time":"2026-10-18T01:48:40.075440212Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS\n"} +{"Time":"2026-10-18T01:48:40.075514847Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy"} +{"Time":"2026-10-18T01:48:40.075528283Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy\n"} +{"Time":"2026-10-18T01:48:40.075549879Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.07570723Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Elapsed":0} +{"Time":"2026-10-18T01:48:40.075729846Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default"} +{"Time":"2026-10-18T01:48:40.075736744Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default\n"} +{"Time":"2026-10-18T01:48:40.07578101Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones"} +{"Time":"2026-10-18T01:48:40.075793715Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Output":"=== RUN TestTokenStatusExactExpiryCapacityAndTombstones\n"} +{"Time":"2026-10-18T01:48:40.075997568Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.076030337Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Elapsed":0} +{"Time":"2026-10-18T01:48:40.076051536Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded"} +{"Time":"2026-10-18T01:48:40.076058515Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded\n"} +{"Time":"2026-10-18T01:48:40.076683108Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.076748852Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Elapsed":0} +{"Time":"2026-10-18T01:48:40.077033548Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http"} +{"Time":"2026-10-18T01:48:40.077070077Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http\n"} +{"Time":"2026-10-18T01:48:40.077340513Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.077452102Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Elapsed":0} +{"Time":"2026-10-18T01:48:40.077573149Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url"} +{"Time":"2026-10-18T01:48:40.077596143Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url\n"} +{"Time":"2026-10-18T01:48:40.077929222Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.07797549Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Elapsed":0} +{"Time":"2026-10-18T01:48:40.077991315Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query"} +{"Time":"2026-10-18T01:48:40.07799714Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query\n"} +{"Time":"2026-10-18T01:48:40.077916853Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Output":"--- PASS: TestTokenStatusExactExpiryCapacityAndTombstones (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.078184136Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Elapsed":0} +{"Time":"2026-10-18T01:48:40.078223866Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.07846276Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Elapsed":0} +{"Time":"2026-10-18T01:48:40.078496088Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large"} +{"Time":"2026-10-18T01:48:40.078529605Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large\n"} +{"Time":"2026-10-18T01:48:40.078640662Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke"} +{"Time":"2026-10-18T01:48:40.078662042Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":"=== RUN TestTokenStatusConcurrentIssueReadAndRevoke\n"} +{"Time":"2026-10-18T01:48:40.078642797Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.078712647Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Elapsed":0} +{"Time":"2026-10-18T01:48:40.078825471Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative"} +{"Time":"2026-10-18T01:48:40.078839682Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative\n"} +{"Time":"2026-10-18T01:48:40.079148385Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.079189053Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Elapsed":0} +{"Time":"2026-10-18T01:48:40.079214698Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity"} +{"Time":"2026-10-18T01:48:40.079219622Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity\n"} +{"Time":"2026-10-18T01:48:40.079497663Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.079530738Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Elapsed":0} +{"Time":"2026-10-18T01:48:40.079584887Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.079698812Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Elapsed":0} +{"Time":"2026-10-18T01:48:40.079826776Z","Action":"output","Package":"keycape/internal/config","Output":"PASS\n"} +{"Time":"2026-10-18T01:48:40.080172046Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.080547044Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.081330785Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.082155552Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.095238697Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.095364136Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.095786783Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.095815999Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.096186112Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.096213317Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.096460354Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.096570878Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.096787513Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.096929967Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.097216601Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.097264248Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.097638466Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.097712597Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.097854253Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.097910323Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.098212189Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.098478017Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.098532382Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.098631536Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.098934714Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099259612Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099307908Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099388594Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099421246Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099439912Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099527181Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.09954343Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099567298Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099656262Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099888907Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099916022Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.099925427Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100073917Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100196381Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100302829Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100499125Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100532796Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100778065Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.100812249Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101080873Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101116455Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101299076Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101327363Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101565689Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101593363Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.101753049Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102056515Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102085596Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102268592Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102287934Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102519984Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102556315Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102836016Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.102868668Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.103046183Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.103064713Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.103280451Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.103365871Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.10339488Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":" token_status_internal_test.go:61: concurrent revoked token accepted\n"} +{"Time":"2026-10-18T01:48:40.103600157Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":"--- FAIL: TestTokenStatusConcurrentIssueReadAndRevoke (0.03s)\n"} +{"Time":"2026-10-18T01:48:40.103634684Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Elapsed":0.03} +{"Time":"2026-10-18T01:48:40.104075815Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile"} +{"Time":"2026-10-18T01:48:40.104105292Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Output":"=== RUN TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile\n"} +{"Time":"2026-10-18T01:48:40.105277418Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Output":"--- PASS: TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile (0.00s)\n"} +{"Time":"2026-10-18T01:48:40.105332828Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Elapsed":0} +{"Time":"2026-10-18T01:48:40.105350818Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant"} +{"Time":"2026-10-18T01:48:40.105355976Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":"=== RUN TestTokenStatusActualIssueRevokeAndFreshGrant\n"} +{"Time":"2026-10-18T01:48:40.397837632Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":" token_status_test.go:139: revoked metadata leaked or remained active\n"} +{"Time":"2026-10-18T01:48:40.397912854Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":"--- FAIL: TestTokenStatusActualIssueRevokeAndFreshGrant (0.29s)\n"} +{"Time":"2026-10-18T01:48:40.397927264Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Elapsed":0.29} +{"Time":"2026-10-18T01:48:40.397935821Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority"} +{"Time":"2026-10-18T01:48:40.397944451Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Output":"=== RUN TestTokenStatusNoCrossClientOrIDTokenAuthority\n"} +{"Time":"2026-10-18T01:48:40.565453191Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Output":"--- PASS: TestTokenStatusNoCrossClientOrIDTokenAuthority (0.17s)\n"} +{"Time":"2026-10-18T01:48:40.565506952Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Elapsed":0.17} +{"Time":"2026-10-18T01:48:40.56551927Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken"} +{"Time":"2026-10-18T01:48:40.565527196Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Output":"=== RUN TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken\n"} +{"Time":"2026-10-18T01:48:40.680590916Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Output":"--- PASS: TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken (0.11s)\n"} +{"Time":"2026-10-18T01:48:40.680729592Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Elapsed":0.11} +{"Time":"2026-10-18T01:48:40.680751946Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse"} +{"Time":"2026-10-18T01:48:40.680760386Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Output":"=== RUN TestTokenStatusRestartAndUnavailableStoreRefuse\n"} +{"Time":"2026-10-18T01:48:40.753639889Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Output":"--- PASS: TestTokenStatusRestartAndUnavailableStoreRefuse (0.07s)\n"} +{"Time":"2026-10-18T01:48:40.753710756Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Elapsed":0.07} +{"Time":"2026-10-18T01:48:40.753728628Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition"} +{"Time":"2026-10-18T01:48:40.753738375Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition\n"} +{"Time":"2026-10-18T01:48:40.753864022Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed"} +{"Time":"2026-10-18T01:48:40.753878756Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed\n"} +{"Time":"2026-10-18T01:48:40.866976317Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed (0.11s)\n"} +{"Time":"2026-10-18T01:48:40.867046859Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Elapsed":0.11} +{"Time":"2026-10-18T01:48:40.867059835Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience"} +{"Time":"2026-10-18T01:48:40.867073117Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience\n"} +{"Time":"2026-10-18T01:48:40.932707099Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience (0.07s)\n"} +{"Time":"2026-10-18T01:48:40.932767632Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Elapsed":0.07} +{"Time":"2026-10-18T01:48:40.932779561Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret"} +{"Time":"2026-10-18T01:48:40.932786523Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret\n"} +{"Time":"2026-10-18T01:48:41.022808976Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret (0.09s)\n"} +{"Time":"2026-10-18T01:48:41.022890528Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Elapsed":0.09} +{"Time":"2026-10-18T01:48:41.022913107Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes"} +{"Time":"2026-10-18T01:48:41.022962973Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes\n"} +{"Time":"2026-10-18T01:48:41.083654782Z","Action":"output","Package":"keycape/internal/config","Output":"ok \tkeycape/internal/config\t1.027s\n"} +{"Time":"2026-10-18T01:48:41.083707533Z","Action":"pass","Package":"keycape/internal/config","Elapsed":1.028} +{"Time":"2026-10-18T01:48:41.127075013Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes (0.10s)\n"} +{"Time":"2026-10-18T01:48:41.1271311Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Elapsed":0.1} +{"Time":"2026-10-18T01:48:41.127148973Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime"} +{"Time":"2026-10-18T01:48:41.127153247Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime\n"} +{"Time":"2026-10-18T01:48:41.181026212Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime (0.05s)\n"} +{"Time":"2026-10-18T01:48:41.181109441Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Elapsed":0.05} +{"Time":"2026-10-18T01:48:41.181133138Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended"} +{"Time":"2026-10-18T01:48:41.181140794Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended\n"} +{"Time":"2026-10-18T01:48:41.42974976Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended (0.25s)\n"} +{"Time":"2026-10-18T01:48:41.429804855Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Elapsed":0.25} +{"Time":"2026-10-18T01:48:41.429821943Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled"} +{"Time":"2026-10-18T01:48:41.429827092Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled\n"} +{"Time":"2026-10-18T01:48:41.506139609Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled (0.08s)\n"} +{"Time":"2026-10-18T01:48:41.506205317Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Elapsed":0.08} +{"Time":"2026-10-18T01:48:41.506224668Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed"} +{"Time":"2026-10-18T01:48:41.506232117Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed\n"} +{"Time":"2026-10-18T01:48:41.633160438Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed (0.13s)\n"} +{"Time":"2026-10-18T01:48:41.633222324Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Elapsed":0.13} +{"Time":"2026-10-18T01:48:41.633235904Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition (0.88s)\n"} +{"Time":"2026-10-18T01:48:41.633253172Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Elapsed":0.88} +{"Time":"2026-10-18T01:48:41.633261098Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect"} +{"Time":"2026-10-18T01:48:41.633268753Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":"=== RUN TestTokenStatusPublicClientMayRevokeButNotIntrospect\n"} +{"Time":"2026-10-18T01:48:41.761459379Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":" token_status_test.go:265: public revocation not enforced\n"} +{"Time":"2026-10-18T01:48:41.76153626Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":"--- FAIL: TestTokenStatusPublicClientMayRevokeButNotIntrospect (0.13s)\n"} +{"Time":"2026-10-18T01:48:41.761547162Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Elapsed":0.13} +{"Time":"2026-10-18T01:48:41.76155911Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct"} +{"Time":"2026-10-18T01:48:41.761567135Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":"=== RUN TestTokenStatusServiceGrantIsCurrentAndDistinct\n"} +{"Time":"2026-10-18T01:48:41.900042565Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":" token_status_test.go:295: service revocation not token-scoped\n"} +{"Time":"2026-10-18T01:48:41.900152269Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":"--- FAIL: TestTokenStatusServiceGrantIsCurrentAndDistinct (0.14s)\n"} +{"Time":"2026-10-18T01:48:41.900164875Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Elapsed":0.14} +{"Time":"2026-10-18T01:48:41.900178572Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests"} +{"Time":"2026-10-18T01:48:41.900183919Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests\n"} +{"Time":"2026-10-18T01:48:41.90019134Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get"} +{"Time":"2026-10-18T01:48:41.900196471Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get\n"} +{"Time":"2026-10-18T01:48:41.93630134Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get (0.04s)\n"} +{"Time":"2026-10-18T01:48:41.936392279Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Elapsed":0.04} +{"Time":"2026-10-18T01:48:41.936975764Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query"} +{"Time":"2026-10-18T01:48:41.937015819Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query\n"} +{"Time":"2026-10-18T01:48:42.056450011Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query (0.12s)\n"} +{"Time":"2026-10-18T01:48:42.056512735Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Elapsed":0.12} +{"Time":"2026-10-18T01:48:42.056525756Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json"} +{"Time":"2026-10-18T01:48:42.056531202Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json\n"} +{"Time":"2026-10-18T01:48:42.228757364Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json (0.17s)\n"} +{"Time":"2026-10-18T01:48:42.228833993Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Elapsed":0.17} +{"Time":"2026-10-18T01:48:42.228879566Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token"} +{"Time":"2026-10-18T01:48:42.228891198Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token\n"} +{"Time":"2026-10-18T01:48:42.511281739Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token (0.28s)\n"} +{"Time":"2026-10-18T01:48:42.511399225Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Elapsed":0.28} +{"Time":"2026-10-18T01:48:42.511442796Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize"} +{"Time":"2026-10-18T01:48:42.511453328Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize\n"} +{"Time":"2026-10-18T01:48:42.624039379Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize (0.11s)\n"} +{"Time":"2026-10-18T01:48:42.624099776Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Elapsed":0.11} +{"Time":"2026-10-18T01:48:42.624110714Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret"} +{"Time":"2026-10-18T01:48:42.624137712Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret\n"} +{"Time":"2026-10-18T01:48:42.720921231Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret (0.10s)\n"} +{"Time":"2026-10-18T01:48:42.720980303Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Elapsed":0.1} +{"Time":"2026-10-18T01:48:42.720991268Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth"} +{"Time":"2026-10-18T01:48:42.720998978Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth\n"} +{"Time":"2026-10-18T01:48:42.795231777Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth (0.07s)\n"} +{"Time":"2026-10-18T01:48:42.795283058Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Elapsed":0.07} +{"Time":"2026-10-18T01:48:42.795297738Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth"} +{"Time":"2026-10-18T01:48:42.795302779Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth\n"} +{"Time":"2026-10-18T01:48:42.930326295Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth (0.13s)\n"} +{"Time":"2026-10-18T01:48:42.930382716Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Elapsed":0.13} +{"Time":"2026-10-18T01:48:42.930393419Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret"} +{"Time":"2026-10-18T01:48:42.930400336Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret\n"} +{"Time":"2026-10-18T01:48:43.13867242Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret (0.21s)\n"} +{"Time":"2026-10-18T01:48:43.138731645Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Elapsed":0.21} +{"Time":"2026-10-18T01:48:43.138743846Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client"} +{"Time":"2026-10-18T01:48:43.13875087Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client\n"} +{"Time":"2026-10-18T01:48:43.239401537Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client (0.10s)\n"} +{"Time":"2026-10-18T01:48:43.239482548Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Elapsed":0.1} +{"Time":"2026-10-18T01:48:43.239495434Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests (1.34s)\n"} +{"Time":"2026-10-18T01:48:43.239503459Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Elapsed":1.34} +{"Time":"2026-10-18T01:48:43.239514848Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses"} +{"Time":"2026-10-18T01:48:43.239522323Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":"=== RUN TestTokenStatusRevocationDuringDirectoryLookupRefuses\n"} +{"Time":"2026-10-18T01:48:43.464899947Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":" token_status_test.go:374: delayed lookup resurrected revoked token\n"} +{"Time":"2026-10-18T01:48:43.464967766Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":"--- FAIL: TestTokenStatusRevocationDuringDirectoryLookupRefuses (0.23s)\n"} +{"Time":"2026-10-18T01:48:43.464980741Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Elapsed":0.23} +{"Time":"2026-10-18T01:48:43.465176154Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding"} +{"Time":"2026-10-18T01:48:43.465194757Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Output":"=== RUN TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding\n"} +{"Time":"2026-10-18T01:48:43.777733468Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Output":" token_status_transport_test.go:64: revocation did not affect HTTPS introspection\n"} +{"Time":"2026-10-18T01:48:43.778231381Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Output":"--- FAIL: TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding (0.31s)\n"} +{"Time":"2026-10-18T01:48:43.778261815Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Elapsed":0.31} +{"Time":"2026-10-18T01:48:43.778380276Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses"} +{"Time":"2026-10-18T01:48:43.778396849Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Output":"=== RUN TestTokenStatusRevocationDuringUserInfoLookupRefuses\n"} +{"Time":"2026-10-18T01:48:43.860160997Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Output":" token_status_transport_test.go:95: delayed UserInfo lookup resurrected revoked token\n"} +{"Time":"2026-10-18T01:48:43.860223531Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Output":"--- FAIL: TestTokenStatusRevocationDuringUserInfoLookupRefuses (0.08s)\n"} +{"Time":"2026-10-18T01:48:43.860232963Z","Action":"fail","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Elapsed":0.08} +{"Time":"2026-10-18T01:48:43.860244037Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim"} +{"Time":"2026-10-18T01:48:43.860270286Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim","Output":"=== RUN TestTokenStatusLegacyGrantHasNoStatusClaim\n"} +{"Time":"2026-10-18T01:48:44.043210266Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim","Output":"--- PASS: TestTokenStatusLegacyGrantHasNoStatusClaim (0.18s)\n"} +{"Time":"2026-10-18T01:48:44.043267895Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim","Elapsed":0.18} +{"Time":"2026-10-18T01:48:44.04328363Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"FAIL\n"} +{"Time":"2026-10-18T01:48:44.049374Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"FAIL\tkeycape/internal/server/oidc\t3.993s\n"} +{"Time":"2026-10-18T01:48:44.04943493Z","Action":"fail","Package":"keycape/internal/server/oidc","Elapsed":3.9939999999999998} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/freeze_candidate.py b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/freeze_candidate.py new file mode 100644 index 0000000..501afc1 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/freeze_candidate.py @@ -0,0 +1,55 @@ +"""Freeze exact owner delta and verify replay from its published Git base.""" +import difflib +import hashlib +import io +import json +from pathlib import Path +import subprocess +import tarfile + +HERE = Path(__file__).resolve().parent +SOURCE = Path('/tmp/custodian-keycape-token-status-r1/source') +OWNER = Path('/home/worsch/key-cape') +baseline = json.loads((HERE / 'baseline.json').read_text()) +commit = baseline['commit'] +assert subprocess.check_output(['git', '-C', str(OWNER), 'rev-parse', 'HEAD'], text=True).strip() == commit +archive = subprocess.check_output(['git', '-C', str(OWNER), 'archive', commit]) +assert 'sha256:' + hashlib.sha256(archive).hexdigest() == baseline['archive_sha256'] +with tarfile.open(fileobj=io.BytesIO(archive)) as tar: + original = {m.name: tar.extractfile(m).read() for m in tar.getmembers() if m.isfile()} +current = {str(p.relative_to(SOURCE)): p.read_bytes() for p in SOURCE.rglob('*') if p.is_file()} +assert not original.keys() - current.keys(), 'unexpected deletion' +rows, patch = [], [] +for name, raw in sorted(current.items()): + before = original.get(name) + if before == raw: + continue + before_path = HERE / 'preimages' / name + after_path = HERE / 'postimages' / name + if before is not None: + before_path.parent.mkdir(parents=True, exist_ok=True) + before_path.write_bytes(before) + after_path.parent.mkdir(parents=True, exist_ok=True) + after_path.write_bytes(raw) + rows.append({'path': name, 'before_sha256': None if before is None else 'sha256:' + hashlib.sha256(before).hexdigest(), + 'sha256': 'sha256:' + hashlib.sha256(raw).hexdigest()}) + patch.append(f'diff --git a/{name} b/{name}\n') + patch.extend(difflib.unified_diff((before or b'').decode().splitlines(keepends=True), raw.decode().splitlines(keepends=True), + fromfile='/dev/null' if before is None else 'a/' + name, tofile='b/' + name)) +assert len(rows) == 18, [row['path'] for row in rows] +(HERE / 'owner-source.patch').write_text(''.join(patch)) +replay = Path('/tmp/custodian-keycape-token-status-r1-replay-r2') +replay.mkdir(exist_ok=False) +with tarfile.open(fileobj=io.BytesIO(archive)) as tar: + tar.extractall(replay, filter='data') +result = subprocess.run(['patch', '-p1', '--batch', '--fuzz=0', '-i', str(HERE / 'owner-source.patch')], cwd=replay, + capture_output=True, text=True, check=True) +assert all((replay / row['path']).read_bytes() == (SOURCE / row['path']).read_bytes() for row in rows) +assert all((replay / name).read_bytes() == raw for name, raw in current.items()) +receipt = {'owner': 'key-cape', 'base_revision': 'git:' + commit, 'files': rows, + 'patch_sha256': 'sha256:' + hashlib.sha256((HERE / 'owner-source.patch').read_bytes()).hexdigest(), + 'replay': {'fuzz': 0, 'all_postimages_exact': True, 'all_unrelated_archive_files_unchanged': True, + 'stdout': result.stdout, 'stderr': result.stderr}, + 'canonical_owner_modified': False, 'independent_owner_assent': False, 'native_admission': False} +(HERE / 'source-manifest.json').write_text(json.dumps(receipt, indent=2) + '\n') +print(json.dumps({'files': len(rows), 'exact_replay': True, 'owner_modified': False})) diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.json new file mode 100644 index 0000000..d5a9e82 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.json @@ -0,0 +1,96 @@ +{ + "returncode": 0, + "seconds": 25.7, + "argv": [ + "bwrap", + "--unshare-net", + "--unshare-pid", + "--die-with-parent", + "--new-session", + "--ro-bind", + "/usr", + "/usr", + "--ro-bind", + "/lib", + "/lib", + "--ro-bind", + "/lib64", + "/lib64", + "--symlink", + "usr/bin", + "/bin", + "--proc", + "/proc", + "--dev", + "/dev", + "--tmpfs", + "/tmp", + "--bind", + "/tmp/custodian-keycape-token-status-r1", + "/work", + "--ro-bind", + "/home/worsch/go/pkg/mod", + "/modules", + "--ro-bind", + "/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64", + "/toolchain", + "--chdir", + "/work/source/src", + "--clearenv", + "--setenv", + "PATH", + "/toolchain/bin:/usr/bin:/bin", + "--setenv", + "HOME", + "/work/home", + "--setenv", + "GOTOOLCHAIN", + "local", + "--setenv", + "GOPROXY", + "off", + "--setenv", + "GOSUMDB", + "off", + "--setenv", + "GOMODCACHE", + "/modules", + "--setenv", + "GOCACHE", + "/work/gocache", + "--setenv", + "GOMAXPROCS", + "4", + "--setenv", + "CGO_ENABLED", + "1", + "--", + "go", + "test", + "-p", + "4", + "-race", + "-json", + "-count=1", + "./..." + ], + "counts_including_subtests": { + "pass": 659, + "fail": 0, + "skip": 6 + }, + "failures": [], + "skips": [ + "keycape/tests/conformance:TestIssuerAdvertisesItself", + "keycape/tests/conformance:TestProfileAuthorizationSurface", + "keycape/tests/conformance:TestPublishedKeysAreUsable", + "keycape/tests/conformance:TestExcludedGrantsAreNotAdvertised", + "keycape/tests/conformance:TestServiceTokenVerifiesAgainstPublishedKeys", + "keycape/internal/server/oidc:TestNativeOptionalEnrollmentAndOldSession" + ], + "source": "/tmp/custodian-keycape-token-status-r1/source", + "native_acceptance": false, + "credentials_accessed": false, + "network": "private empty namespace; fixture loopback only", + "home": "new empty sandbox HOME" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.jsonl b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.jsonl new file mode 100644 index 0000000..04c3079 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-attempt1.jsonl @@ -0,0 +1,2772 @@ +{"Time":"2026-10-18T01:46:02.925207639Z","Action":"start","Package":"keycape/cmd/keycape"} +{"Time":"2026-10-18T01:46:02.934557115Z","Action":"start","Package":"keycape/cmd/keycape-to-keycloak"} +{"Time":"2026-10-18T01:46:02.93460092Z","Action":"output","Package":"keycape/cmd/keycape-to-keycloak","Output":"? \tkeycape/cmd/keycape-to-keycloak\t[no test files]\n"} +{"Time":"2026-10-18T01:46:02.934612571Z","Action":"skip","Package":"keycape/cmd/keycape-to-keycloak","Elapsed":0} +{"Time":"2026-10-18T01:46:02.934624221Z","Action":"start","Package":"keycape/cmd/lldap-export"} +{"Time":"2026-10-18T01:46:02.949048114Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigAcceptsValidConfigWithoutSecretsOrKey"} +{"Time":"2026-10-18T01:46:02.949174121Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigAcceptsValidConfigWithoutSecretsOrKey","Output":"=== RUN TestCheckConfigAcceptsValidConfigWithoutSecretsOrKey\n"} +{"Time":"2026-10-18T01:46:02.951771106Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigAcceptsValidConfigWithoutSecretsOrKey","Output":"--- PASS: TestCheckConfigAcceptsValidConfigWithoutSecretsOrKey (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.951841026Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigAcceptsValidConfigWithoutSecretsOrKey","Elapsed":0} +{"Time":"2026-10-18T01:46:02.951857924Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup"} +{"Time":"2026-10-18T01:46:02.951872803Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup","Output":"=== RUN TestCheckConfigRefusesWhatStopsStartup\n"} +{"Time":"2026-10-18T01:46:02.952122545Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/duplicate_client"} +{"Time":"2026-10-18T01:46:02.952222771Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/duplicate_client","Output":"=== RUN TestCheckConfigRefusesWhatStopsStartup/duplicate_client\n"} +{"Time":"2026-10-18T01:46:02.954949418Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/duplicate_client","Output":"--- PASS: TestCheckConfigRefusesWhatStopsStartup/duplicate_client (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.9550062Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/duplicate_client","Elapsed":0} +{"Time":"2026-10-18T01:46:02.955140936Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/query_in_redirect"} +{"Time":"2026-10-18T01:46:02.955171143Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/query_in_redirect","Output":"=== RUN TestCheckConfigRefusesWhatStopsStartup/query_in_redirect\n"} +{"Time":"2026-10-18T01:46:02.957995503Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/query_in_redirect","Output":"--- PASS: TestCheckConfigRefusesWhatStopsStartup/query_in_redirect (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.958096641Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/query_in_redirect","Elapsed":0} +{"Time":"2026-10-18T01:46:02.958114648Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/secret_env_not_provided"} +{"Time":"2026-10-18T01:46:02.958120293Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/secret_env_not_provided","Output":"=== RUN TestCheckConfigRefusesWhatStopsStartup/secret_env_not_provided\n"} +{"Time":"2026-10-18T01:46:02.958137047Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword"} +{"Time":"2026-10-18T01:46:02.958145478Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword","Output":"=== RUN TestResolveBindPassword\n"} +{"Time":"2026-10-18T01:46:02.958213892Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/environment_wins"} +{"Time":"2026-10-18T01:46:02.958241223Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/environment_wins","Output":"=== RUN TestResolveBindPassword/environment_wins\n"} +{"Time":"2026-10-18T01:46:02.958599425Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/environment_wins","Output":"--- PASS: TestResolveBindPassword/environment_wins (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.958726478Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/environment_wins","Elapsed":0} +{"Time":"2026-10-18T01:46:02.958845893Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/file,_trailing_newline_trimmed"} +{"Time":"2026-10-18T01:46:02.958856092Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/file,_trailing_newline_trimmed","Output":"=== RUN TestResolveBindPassword/file,_trailing_newline_trimmed\n"} +{"Time":"2026-10-18T01:46:02.96010723Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/file,_trailing_newline_trimmed","Output":"--- PASS: TestResolveBindPassword/file,_trailing_newline_trimmed (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.960970719Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/file,_trailing_newline_trimmed","Elapsed":0} +{"Time":"2026-10-18T01:46:02.961023425Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/deprecated_flag_still_works"} +{"Time":"2026-10-18T01:46:02.961038348Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/deprecated_flag_still_works","Output":"=== RUN TestResolveBindPassword/deprecated_flag_still_works\n"} +{"Time":"2026-10-18T01:46:02.961047816Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/deprecated_flag_still_works","Output":"lldap-export: warning: --bind-pw exposes the password to any local user via ps; prefer KEYCAPE_LLDAP_BIND_PW or --bind-pw-file\n"} +{"Time":"2026-10-18T01:46:02.961059458Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/deprecated_flag_still_works","Output":"--- PASS: TestResolveBindPassword/deprecated_flag_still_works (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.961077348Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/deprecated_flag_still_works","Elapsed":0} +{"Time":"2026-10-18T01:46:02.961083191Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/missing_file_is_an_error,_not_an_empty_password"} +{"Time":"2026-10-18T01:46:02.961088538Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/missing_file_is_an_error,_not_an_empty_password","Output":"=== RUN TestResolveBindPassword/missing_file_is_an_error,_not_an_empty_password\n"} +{"Time":"2026-10-18T01:46:02.961115518Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/secret_env_not_provided","Output":"--- PASS: TestCheckConfigRefusesWhatStopsStartup/secret_env_not_provided (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.961276142Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/secret_env_not_provided","Elapsed":0} +{"Time":"2026-10-18T01:46:02.96130552Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/policy_store_client_missing"} +{"Time":"2026-10-18T01:46:02.961321346Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/policy_store_client_missing","Output":"=== RUN TestCheckConfigRefusesWhatStopsStartup/policy_store_client_missing\n"} +{"Time":"2026-10-18T01:46:02.96220909Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/missing_file_is_an_error,_not_an_empty_password","Output":"--- PASS: TestResolveBindPassword/missing_file_is_an_error,_not_an_empty_password (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.962301291Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/missing_file_is_an_error,_not_an_empty_password","Elapsed":0} +{"Time":"2026-10-18T01:46:02.96235204Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/conflicting_sources_are_rejected"} +{"Time":"2026-10-18T01:46:02.96236993Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/conflicting_sources_are_rejected","Output":"=== RUN TestResolveBindPassword/conflicting_sources_are_rejected\n"} +{"Time":"2026-10-18T01:46:02.962745427Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/conflicting_sources_are_rejected","Output":"--- PASS: TestResolveBindPassword/conflicting_sources_are_rejected (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.962790522Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/conflicting_sources_are_rejected","Elapsed":0} +{"Time":"2026-10-18T01:46:02.962808566Z","Action":"run","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/no_password_at_all_is_an_error"} +{"Time":"2026-10-18T01:46:02.962815635Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/no_password_at_all_is_an_error","Output":"=== RUN TestResolveBindPassword/no_password_at_all_is_an_error\n"} +{"Time":"2026-10-18T01:46:02.963108522Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/no_password_at_all_is_an_error","Output":"--- PASS: TestResolveBindPassword/no_password_at_all_is_an_error (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.96315588Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword/no_password_at_all_is_an_error","Elapsed":0} +{"Time":"2026-10-18T01:46:02.963275169Z","Action":"output","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword","Output":"--- PASS: TestResolveBindPassword (0.01s)\n"} +{"Time":"2026-10-18T01:46:02.963333024Z","Action":"pass","Package":"keycape/cmd/lldap-export","Test":"TestResolveBindPassword","Elapsed":0.01} +{"Time":"2026-10-18T01:46:02.963444748Z","Action":"output","Package":"keycape/cmd/lldap-export","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:02.964542612Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/policy_store_client_missing","Output":"--- PASS: TestCheckConfigRefusesWhatStopsStartup/policy_store_client_missing (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.9645998Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/policy_store_client_missing","Elapsed":0} +{"Time":"2026-10-18T01:46:02.964713706Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/explicit_mfa_false_on_policy_client"} +{"Time":"2026-10-18T01:46:02.964723381Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/explicit_mfa_false_on_policy_client","Output":"=== RUN TestCheckConfigRefusesWhatStopsStartup/explicit_mfa_false_on_policy_client\n"} +{"Time":"2026-10-18T01:46:02.967860004Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/explicit_mfa_false_on_policy_client","Output":"--- PASS: TestCheckConfigRefusesWhatStopsStartup/explicit_mfa_false_on_policy_client (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.967923928Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup/explicit_mfa_false_on_policy_client","Elapsed":0} +{"Time":"2026-10-18T01:46:02.967955344Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup","Output":"--- PASS: TestCheckConfigRefusesWhatStopsStartup (0.02s)\n"} +{"Time":"2026-10-18T01:46:02.967974199Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigRefusesWhatStopsStartup","Elapsed":0.02} +{"Time":"2026-10-18T01:46:02.968172921Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestCheckConfigWithoutEnvNamesOnlyListsThem"} +{"Time":"2026-10-18T01:46:02.96821651Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigWithoutEnvNamesOnlyListsThem","Output":"=== RUN TestCheckConfigWithoutEnvNamesOnlyListsThem\n"} +{"Time":"2026-10-18T01:46:02.970715857Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestCheckConfigWithoutEnvNamesOnlyListsThem","Output":"--- PASS: TestCheckConfigWithoutEnvNamesOnlyListsThem (0.00s)\n"} +{"Time":"2026-10-18T01:46:02.97076905Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestCheckConfigWithoutEnvNamesOnlyListsThem","Elapsed":0} +{"Time":"2026-10-18T01:46:02.970784767Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationAudienceAndScopeIsolation"} +{"Time":"2026-10-18T01:46:02.970790024Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationAudienceAndScopeIsolation","Output":"=== RUN TestServiceRegistrationAudienceAndScopeIsolation\n"} +{"Time":"2026-10-18T01:46:02.980004186Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationAudienceAndScopeIsolation","Output":"--- PASS: TestServiceRegistrationAudienceAndScopeIsolation (0.01s)\n"} +{"Time":"2026-10-18T01:46:02.980058497Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationAudienceAndScopeIsolation","Elapsed":0.01} +{"Time":"2026-10-18T01:46:02.980077127Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationTenantsAreExactPerDecision"} +{"Time":"2026-10-18T01:46:02.980086604Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationTenantsAreExactPerDecision","Output":"=== RUN TestServiceRegistrationTenantsAreExactPerDecision\n"} +{"Time":"2026-10-18T01:46:02.986130602Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationTenantsAreExactPerDecision","Output":"--- PASS: TestServiceRegistrationTenantsAreExactPerDecision (0.01s)\n"} +{"Time":"2026-10-18T01:46:02.986190495Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestServiceRegistrationTenantsAreExactPerDecision","Elapsed":0.01} +{"Time":"2026-10-18T01:46:02.986203074Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestApproverRegistrationShapeIsExact"} +{"Time":"2026-10-18T01:46:02.986218566Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestApproverRegistrationShapeIsExact","Output":"=== RUN TestApproverRegistrationShapeIsExact\n"} +{"Time":"2026-10-18T01:46:02.992228544Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestApproverRegistrationShapeIsExact","Output":"--- PASS: TestApproverRegistrationShapeIsExact (0.01s)\n"} +{"Time":"2026-10-18T01:46:02.99228776Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestApproverRegistrationShapeIsExact","Elapsed":0.01} +{"Time":"2026-10-18T01:46:02.992298454Z","Action":"run","Package":"keycape/cmd/keycape","Test":"TestSittingRequesterRegistrationShapeIsExact"} +{"Time":"2026-10-18T01:46:02.992304893Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestSittingRequesterRegistrationShapeIsExact","Output":"=== RUN TestSittingRequesterRegistrationShapeIsExact\n"} +{"Time":"2026-10-18T01:46:02.992422885Z","Action":"start","Package":"keycape/cmd/lldap-to-ldap"} +{"Time":"2026-10-18T01:46:02.992443579Z","Action":"output","Package":"keycape/cmd/lldap-to-ldap","Output":"? \tkeycape/cmd/lldap-to-ldap\t[no test files]\n"} +{"Time":"2026-10-18T01:46:02.99245403Z","Action":"skip","Package":"keycape/cmd/lldap-to-ldap","Elapsed":0} +{"Time":"2026-10-18T01:46:02.992465356Z","Action":"start","Package":"keycape/cmd/validator"} +{"Time":"2026-10-18T01:46:02.99247137Z","Action":"output","Package":"keycape/cmd/validator","Output":"? \tkeycape/cmd/validator\t[no test files]\n"} +{"Time":"2026-10-18T01:46:02.992476249Z","Action":"skip","Package":"keycape/cmd/validator","Elapsed":0} +{"Time":"2026-10-18T01:46:02.992485816Z","Action":"start","Package":"keycape/internal/adapters/authelia"} +{"Time":"2026-10-18T01:46:02.997807994Z","Action":"output","Package":"keycape/cmd/keycape","Test":"TestSittingRequesterRegistrationShapeIsExact","Output":"--- PASS: TestSittingRequesterRegistrationShapeIsExact (0.01s)\n"} +{"Time":"2026-10-18T01:46:02.997889879Z","Action":"pass","Package":"keycape/cmd/keycape","Test":"TestSittingRequesterRegistrationShapeIsExact","Elapsed":0.01} +{"Time":"2026-10-18T01:46:02.997900249Z","Action":"output","Package":"keycape/cmd/keycape","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:03.018657481Z","Action":"start","Package":"keycape/internal/adapters/lldap"} +{"Time":"2026-10-18T01:46:03.036474698Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestUserBaseDNDefaultsToLLDAPsPeopleBranch"} +{"Time":"2026-10-18T01:46:03.036522165Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestUserBaseDNDefaultsToLLDAPsPeopleBranch","Output":"=== RUN TestUserBaseDNDefaultsToLLDAPsPeopleBranch\n"} +{"Time":"2026-10-18T01:46:03.036689451Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestUserBaseDNDefaultsToLLDAPsPeopleBranch","Output":"--- PASS: TestUserBaseDNDefaultsToLLDAPsPeopleBranch (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.036709532Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestUserBaseDNDefaultsToLLDAPsPeopleBranch","Elapsed":0} +{"Time":"2026-10-18T01:46:03.036781787Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestExplicitOUOverridesDefaults"} +{"Time":"2026-10-18T01:46:03.036795169Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestExplicitOUOverridesDefaults","Output":"=== RUN TestExplicitOUOverridesDefaults\n"} +{"Time":"2026-10-18T01:46:03.036956551Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestExplicitOUOverridesDefaults","Output":"--- PASS: TestExplicitOUOverridesDefaults (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.03696968Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestExplicitOUOverridesDefaults","Elapsed":0} +{"Time":"2026-10-18T01:46:03.037036389Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestRailianceAdminGroupIsExplicitAndScoped"} +{"Time":"2026-10-18T01:46:03.037046272Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestRailianceAdminGroupIsExplicitAndScoped","Output":"=== RUN TestRailianceAdminGroupIsExplicitAndScoped\n"} +{"Time":"2026-10-18T01:46:03.037232902Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestRailianceAdminGroupIsExplicitAndScoped","Output":"--- PASS: TestRailianceAdminGroupIsExplicitAndScoped (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.037256482Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestRailianceAdminGroupIsExplicitAndScoped","Elapsed":0} +{"Time":"2026-10-18T01:46:03.037380064Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_Success"} +{"Time":"2026-10-18T01:46:03.03739038Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_Success","Output":"=== RUN TestLookupUser_Success\n"} +{"Time":"2026-10-18T01:46:03.037570409Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_Success","Output":"--- PASS: TestLookupUser_Success (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.037583466Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_Success","Elapsed":0} +{"Time":"2026-10-18T01:46:03.037616036Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsExplicitTenantAdminGroup"} +{"Time":"2026-10-18T01:46:03.037621149Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsExplicitTenantAdminGroup","Output":"=== RUN TestLookupUser_MapsExplicitTenantAdminGroup\n"} +{"Time":"2026-10-18T01:46:03.037805931Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsExplicitTenantAdminGroup","Output":"--- PASS: TestLookupUser_MapsExplicitTenantAdminGroup (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.037819754Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsExplicitTenantAdminGroup","Elapsed":0} +{"Time":"2026-10-18T01:46:03.03783998Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_IgnoresAmbiguousTenantGroups"} +{"Time":"2026-10-18T01:46:03.03784511Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_IgnoresAmbiguousTenantGroups","Output":"=== RUN TestLookupUser_IgnoresAmbiguousTenantGroups\n"} +{"Time":"2026-10-18T01:46:03.038026601Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_IgnoresAmbiguousTenantGroups","Output":"--- PASS: TestLookupUser_IgnoresAmbiguousTenantGroups (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.038039991Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_IgnoresAmbiguousTenantGroups","Elapsed":0} +{"Time":"2026-10-18T01:46:03.038090984Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsEstablishedPlatformAdminGroup"} +{"Time":"2026-10-18T01:46:03.038100623Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsEstablishedPlatformAdminGroup","Output":"=== RUN TestLookupUser_MapsEstablishedPlatformAdminGroup\n"} +{"Time":"2026-10-18T01:46:03.038260138Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsEstablishedPlatformAdminGroup","Output":"--- PASS: TestLookupUser_MapsEstablishedPlatformAdminGroup (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.038273078Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_MapsEstablishedPlatformAdminGroup","Elapsed":0} +{"Time":"2026-10-18T01:46:03.038279643Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_DisplayName_FallsBackToSN"} +{"Time":"2026-10-18T01:46:03.038283809Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_DisplayName_FallsBackToSN","Output":"=== RUN TestLookupUser_DisplayName_FallsBackToSN\n"} +{"Time":"2026-10-18T01:46:03.03847015Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_DisplayName_FallsBackToSN","Output":"--- PASS: TestLookupUser_DisplayName_FallsBackToSN (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.038483766Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_DisplayName_FallsBackToSN","Elapsed":0} +{"Time":"2026-10-18T01:46:03.038497328Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_NotFound"} +{"Time":"2026-10-18T01:46:03.03850144Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_NotFound","Output":"=== RUN TestLookupUser_NotFound\n"} +{"Time":"2026-10-18T01:46:03.038695772Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_NotFound","Output":"--- PASS: TestLookupUser_NotFound (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.038716836Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_NotFound","Elapsed":0} +{"Time":"2026-10-18T01:46:03.038734501Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_ValidationWarningDoesNotBlockRuntimeLogin"} +{"Time":"2026-10-18T01:46:03.038739334Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_ValidationWarningDoesNotBlockRuntimeLogin","Output":"=== RUN TestLookupUser_ValidationWarningDoesNotBlockRuntimeLogin\n"} +{"Time":"2026-10-18T01:46:03.038986848Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_ValidationWarningDoesNotBlockRuntimeLogin","Output":"--- PASS: TestLookupUser_ValidationWarningDoesNotBlockRuntimeLogin (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.039002357Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupUser_ValidationWarningDoesNotBlockRuntimeLogin","Elapsed":0} +{"Time":"2026-10-18T01:46:03.039079491Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Success"} +{"Time":"2026-10-18T01:46:03.0391006Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Success","Output":"=== RUN TestLookupGroups_Success\n"} +{"Time":"2026-10-18T01:46:03.039309927Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Success","Output":"--- PASS: TestLookupGroups_Success (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.039323561Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Success","Elapsed":0} +{"Time":"2026-10-18T01:46:03.039398683Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Empty"} +{"Time":"2026-10-18T01:46:03.03940835Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Empty","Output":"=== RUN TestLookupGroups_Empty\n"} +{"Time":"2026-10-18T01:46:03.039581147Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Empty","Output":"--- PASS: TestLookupGroups_Empty (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.039593862Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLookupGroups_Empty","Elapsed":0} +{"Time":"2026-10-18T01:46:03.039670517Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_Success"} +{"Time":"2026-10-18T01:46:03.039680229Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_Success","Output":"=== RUN TestValidatePassword_Success\n"} +{"Time":"2026-10-18T01:46:03.039878058Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_Success","Output":"--- PASS: TestValidatePassword_Success (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.039890926Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_Success","Elapsed":0} +{"Time":"2026-10-18T01:46:03.039979322Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_WrongPassword"} +{"Time":"2026-10-18T01:46:03.039989367Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_WrongPassword","Output":"=== RUN TestValidatePassword_WrongPassword\n"} +{"Time":"2026-10-18T01:46:03.040233094Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_WrongPassword","Output":"--- PASS: TestValidatePassword_WrongPassword (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.040273852Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_WrongPassword","Elapsed":0} +{"Time":"2026-10-18T01:46:03.040282725Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_BindFailure"} +{"Time":"2026-10-18T01:46:03.040286819Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_BindFailure","Output":"=== RUN TestValidatePassword_BindFailure\n"} +{"Time":"2026-10-18T01:46:03.040479094Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_BindFailure","Output":"--- PASS: TestValidatePassword_BindFailure (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.040490375Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_BindFailure","Elapsed":0} +{"Time":"2026-10-18T01:46:03.040497056Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_UserNotFound"} +{"Time":"2026-10-18T01:46:03.040500997Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_UserNotFound","Output":"=== RUN TestValidatePassword_UserNotFound\n"} +{"Time":"2026-10-18T01:46:03.040668979Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_UserNotFound","Output":"--- PASS: TestValidatePassword_UserNotFound (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.040682379Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestValidatePassword_UserNotFound","Elapsed":0} +{"Time":"2026-10-18T01:46:03.040699097Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_ReturnsAllGroupsWithMembers"} +{"Time":"2026-10-18T01:46:03.040715878Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_ReturnsAllGroupsWithMembers","Output":"=== RUN TestLDAPAdapter_ListGroups_ReturnsAllGroupsWithMembers\n"} +{"Time":"2026-10-18T01:46:03.040909496Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_ReturnsAllGroupsWithMembers","Output":"--- PASS: TestLDAPAdapter_ListGroups_ReturnsAllGroupsWithMembers (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.040920957Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_ReturnsAllGroupsWithMembers","Elapsed":0} +{"Time":"2026-10-18T01:46:03.040928288Z","Action":"run","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_PropagatesSearchError"} +{"Time":"2026-10-18T01:46:03.040932265Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_PropagatesSearchError","Output":"=== RUN TestLDAPAdapter_ListGroups_PropagatesSearchError\n"} +{"Time":"2026-10-18T01:46:03.04112593Z","Action":"output","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_PropagatesSearchError","Output":"--- PASS: TestLDAPAdapter_ListGroups_PropagatesSearchError (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.0411485Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Test":"TestLDAPAdapter_ListGroups_PropagatesSearchError","Elapsed":0} +{"Time":"2026-10-18T01:46:03.041165209Z","Action":"output","Package":"keycape/internal/adapters/lldap","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:03.16963424Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_ContainsRequiredParams"} +{"Time":"2026-10-18T01:46:03.16969159Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_ContainsRequiredParams","Output":"=== RUN TestAuthorizeURL_ContainsRequiredParams\n"} +{"Time":"2026-10-18T01:46:03.169757524Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_ContainsRequiredParams","Output":"--- PASS: TestAuthorizeURL_ContainsRequiredParams (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.169951215Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_ContainsRequiredParams","Elapsed":0} +{"Time":"2026-10-18T01:46:03.169987139Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBaseURL"} +{"Time":"2026-10-18T01:46:03.16999759Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBaseURL","Output":"=== RUN TestAuthorizeURL_UsesBaseURL\n"} +{"Time":"2026-10-18T01:46:03.170016301Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBaseURL","Output":"--- PASS: TestAuthorizeURL_UsesBaseURL (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.170026031Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBaseURL","Elapsed":0} +{"Time":"2026-10-18T01:46:03.17004191Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured"} +{"Time":"2026-10-18T01:46:03.170065355Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured","Output":"=== RUN TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured\n"} +{"Time":"2026-10-18T01:46:03.170082848Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured","Output":"--- PASS: TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.170166303Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURL_UsesBrowserBaseURLWhenConfigured","Elapsed":0} +{"Time":"2026-10-18T01:46:03.170199486Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_PreferredUsername"} +{"Time":"2026-10-18T01:46:03.170241281Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_PreferredUsername","Output":"=== RUN TestHandleCallback_Success_PreferredUsername\n"} +{"Time":"2026-10-18T01:46:03.173687358Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_PreferredUsername","Output":"--- PASS: TestHandleCallback_Success_PreferredUsername (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.17377919Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_PreferredUsername","Elapsed":0} +{"Time":"2026-10-18T01:46:03.173787729Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_UsesTokenBaseURLWhenConfigured"} +{"Time":"2026-10-18T01:46:03.17379295Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_UsesTokenBaseURLWhenConfigured","Output":"=== RUN TestHandleCallback_UsesTokenBaseURLWhenConfigured\n"} +{"Time":"2026-10-18T01:46:03.1767471Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_UsesTokenBaseURLWhenConfigured","Output":"--- PASS: TestHandleCallback_UsesTokenBaseURLWhenConfigured (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.176805244Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_UsesTokenBaseURLWhenConfigured","Elapsed":0} +{"Time":"2026-10-18T01:46:03.176816182Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP"} +{"Time":"2026-10-18T01:46:03.176827841Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP","Output":"=== RUN TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP\n"} +{"Time":"2026-10-18T01:46:03.176839861Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/_https://authelia.local"} +{"Time":"2026-10-18T01:46:03.176846362Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/_https://authelia.local","Output":"=== RUN TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/_https://authelia.local\n"} +{"Time":"2026-10-18T01:46:03.179568922Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/_https://authelia.local","Output":"--- PASS: TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/_https://authelia.local (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.179591033Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/_https://authelia.local","Elapsed":0} +{"Time":"2026-10-18T01:46:03.179599581Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/http://localhost:9091_http://authelia:9091"} +{"Time":"2026-10-18T01:46:03.179605406Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/http://localhost:9091_http://authelia:9091","Output":"=== RUN TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/http://localhost:9091_http://authelia:9091\n"} +{"Time":"2026-10-18T01:46:03.182261608Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/http://localhost:9091_http://authelia:9091","Output":"--- PASS: TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/http://localhost:9091_http://authelia:9091 (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.182296802Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP/http://localhost:9091_http://authelia:9091","Elapsed":0} +{"Time":"2026-10-18T01:46:03.18232527Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP","Output":"--- PASS: TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP (0.01s)\n"} +{"Time":"2026-10-18T01:46:03.182333205Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestTokenRequestOmitsForwardedHeadersForSameOriginAndPlainHTTP","Elapsed":0.01} +{"Time":"2026-10-18T01:46:03.182346136Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestDiscoveryFetch_CarriesPublicOriginHeaders"} +{"Time":"2026-10-18T01:46:03.182352006Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestDiscoveryFetch_CarriesPublicOriginHeaders","Output":"=== RUN TestDiscoveryFetch_CarriesPublicOriginHeaders\n"} +{"Time":"2026-10-18T01:46:03.18503299Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestDiscoveryFetch_CarriesPublicOriginHeaders","Output":"--- PASS: TestDiscoveryFetch_CarriesPublicOriginHeaders (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.185088987Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestDiscoveryFetch_CarriesPublicOriginHeaders","Elapsed":0} +{"Time":"2026-10-18T01:46:03.18509767Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_FallsBackToSub"} +{"Time":"2026-10-18T01:46:03.185103712Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_FallsBackToSub","Output":"=== RUN TestHandleCallback_Success_FallsBackToSub\n"} +{"Time":"2026-10-18T01:46:03.188672025Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_FallsBackToSub","Output":"--- PASS: TestHandleCallback_Success_FallsBackToSub (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.188729122Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_Success_FallsBackToSub","Elapsed":0} +{"Time":"2026-10-18T01:46:03.188738319Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CallbackError_ReturnsErrAuthFailed"} +{"Time":"2026-10-18T01:46:03.188744054Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CallbackError_ReturnsErrAuthFailed","Output":"=== RUN TestHandleCallback_CallbackError_ReturnsErrAuthFailed\n"} +{"Time":"2026-10-18T01:46:03.188977231Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CallbackError_ReturnsErrAuthFailed","Output":"--- PASS: TestHandleCallback_CallbackError_ReturnsErrAuthFailed (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.188997196Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CallbackError_ReturnsErrAuthFailed","Elapsed":0} +{"Time":"2026-10-18T01:46:03.189174651Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_HTTPError_ReturnsErrAuthFailed"} +{"Time":"2026-10-18T01:46:03.189206238Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_HTTPError_ReturnsErrAuthFailed","Output":"=== RUN TestHandleCallback_HTTPError_ReturnsErrAuthFailed\n"} +{"Time":"2026-10-18T01:46:03.189552171Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_HTTPError_ReturnsErrAuthFailed","Output":"--- PASS: TestHandleCallback_HTTPError_ReturnsErrAuthFailed (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.189589782Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_HTTPError_ReturnsErrAuthFailed","Elapsed":0} +{"Time":"2026-10-18T01:46:03.189748657Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_MissingUsernameClaim_ReturnsErrAuthFailed"} +{"Time":"2026-10-18T01:46:03.189769261Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_MissingUsernameClaim_ReturnsErrAuthFailed","Output":"=== RUN TestHandleCallback_MissingUsernameClaim_ReturnsErrAuthFailed\n"} +{"Time":"2026-10-18T01:46:03.193100756Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_MissingUsernameClaim_ReturnsErrAuthFailed","Output":"--- PASS: TestHandleCallback_MissingUsernameClaim_ReturnsErrAuthFailed (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.19314972Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_MissingUsernameClaim_ReturnsErrAuthFailed","Elapsed":0} +{"Time":"2026-10-18T01:46:03.19316945Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_TokenExchangeNetworkError_ReturnsErrAuthFailed"} +{"Time":"2026-10-18T01:46:03.193182894Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_TokenExchangeNetworkError_ReturnsErrAuthFailed","Output":"=== RUN TestHandleCallback_TokenExchangeNetworkError_ReturnsErrAuthFailed\n"} +{"Time":"2026-10-18T01:46:03.193363068Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_TokenExchangeNetworkError_ReturnsErrAuthFailed","Output":"--- PASS: TestHandleCallback_TokenExchangeNetworkError_ReturnsErrAuthFailed (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.193380589Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_TokenExchangeNetworkError_ReturnsErrAuthFailed","Elapsed":0} +{"Time":"2026-10-18T01:46:03.193388091Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_AuthResultContainsNoRawTokens"} +{"Time":"2026-10-18T01:46:03.193394078Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_AuthResultContainsNoRawTokens","Output":"=== RUN TestHandleCallback_AuthResultContainsNoRawTokens\n"} +{"Time":"2026-10-18T01:46:03.198239813Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_AuthResultContainsNoRawTokens","Output":"--- PASS: TestHandleCallback_AuthResultContainsNoRawTokens (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.198282996Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_AuthResultContainsNoRawTokens","Elapsed":0} +{"Time":"2026-10-18T01:46:03.19829425Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURLTranslatesFreshLoginToBoundedMaxAge"} +{"Time":"2026-10-18T01:46:03.198299498Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURLTranslatesFreshLoginToBoundedMaxAge","Output":"=== RUN TestAuthorizeURLTranslatesFreshLoginToBoundedMaxAge\n"} +{"Time":"2026-10-18T01:46:03.198821705Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURLTranslatesFreshLoginToBoundedMaxAge","Output":"--- PASS: TestAuthorizeURLTranslatesFreshLoginToBoundedMaxAge (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.198841155Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestAuthorizeURLTranslatesFreshLoginToBoundedMaxAge","Elapsed":0} +{"Time":"2026-10-18T01:46:03.198865799Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CarriesUpstreamAuthTime"} +{"Time":"2026-10-18T01:46:03.198872517Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CarriesUpstreamAuthTime","Output":"=== RUN TestHandleCallback_CarriesUpstreamAuthTime\n"} +{"Time":"2026-10-18T01:46:03.204466295Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CarriesUpstreamAuthTime","Output":"--- PASS: TestHandleCallback_CarriesUpstreamAuthTime (0.01s)\n"} +{"Time":"2026-10-18T01:46:03.204532013Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestHandleCallback_CarriesUpstreamAuthTime","Elapsed":0.01} +{"Time":"2026-10-18T01:46:03.204543672Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens"} +{"Time":"2026-10-18T01:46:03.204566567Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens","Output":"=== RUN TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens\n"} +{"Time":"2026-10-18T01:46:03.204577658Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/https://authelia.local"} +{"Time":"2026-10-18T01:46:03.204586396Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/https://authelia.local","Output":"=== RUN TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/https://authelia.local\n"} +{"Time":"2026-10-18T01:46:03.207921993Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/https://authelia.local","Output":"--- PASS: TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/https://authelia.local (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.207957702Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/https://authelia.local","Elapsed":0} +{"Time":"2026-10-18T01:46:03.207968207Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/http://authelia.sso.svc.cluster.local:9091"} +{"Time":"2026-10-18T01:46:03.207975953Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/http://authelia.sso.svc.cluster.local:9091","Output":"=== RUN TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/http://authelia.sso.svc.cluster.local:9091\n"} +{"Time":"2026-10-18T01:46:03.211015974Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/http://authelia.sso.svc.cluster.local:9091","Output":"--- PASS: TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/http://authelia.sso.svc.cluster.local:9091 (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.211060104Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens/http://authelia.sso.svc.cluster.local:9091","Elapsed":0} +{"Time":"2026-10-18T01:46:03.211071132Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens","Output":"--- PASS: TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens (0.01s)\n"} +{"Time":"2026-10-18T01:46:03.211082485Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeVerifiesSignedIssuerWithoutDisclosingTokens","Elapsed":0.01} +{"Time":"2026-10-18T01:46:03.211091962Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRefusesUnboundCallbacksBeforeExchange"} +{"Time":"2026-10-18T01:46:03.211098869Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRefusesUnboundCallbacksBeforeExchange","Output":"=== RUN TestIssuerProbeRefusesUnboundCallbacksBeforeExchange\n"} +{"Time":"2026-10-18T01:46:03.214313672Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRefusesUnboundCallbacksBeforeExchange","Output":"--- PASS: TestIssuerProbeRefusesUnboundCallbacksBeforeExchange (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.214348118Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRefusesUnboundCallbacksBeforeExchange","Elapsed":0} +{"Time":"2026-10-18T01:46:03.214356387Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory"} +{"Time":"2026-10-18T01:46:03.214362347Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory\n"} +{"Time":"2026-10-18T01:46:03.281017519Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unreviewed_issuer"} +{"Time":"2026-10-18T01:46:03.281072254Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unreviewed_issuer","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unreviewed_issuer\n"} +{"Time":"2026-10-18T01:46:03.283826744Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unreviewed_issuer","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unreviewed_issuer (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.283852164Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unreviewed_issuer","Elapsed":0} +{"Time":"2026-10-18T01:46:03.283868205Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_audience"} +{"Time":"2026-10-18T01:46:03.283879215Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_audience","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_audience\n"} +{"Time":"2026-10-18T01:46:03.287075427Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_audience","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_audience (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.287129332Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_audience","Elapsed":0} +{"Time":"2026-10-18T01:46:03.287139531Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/expired"} +{"Time":"2026-10-18T01:46:03.2871466Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/expired","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/expired\n"} +{"Time":"2026-10-18T01:46:03.290452062Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/expired","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/expired (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.290512Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/expired","Elapsed":0} +{"Time":"2026-10-18T01:46:03.290520485Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_nonce"} +{"Time":"2026-10-18T01:46:03.290525886Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_nonce","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_nonce\n"} +{"Time":"2026-10-18T01:46:03.294326933Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_nonce","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_nonce (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.294396897Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/wrong_nonce","Elapsed":0} +{"Time":"2026-10-18T01:46:03.294411884Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/forged_signature"} +{"Time":"2026-10-18T01:46:03.294419567Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/forged_signature","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/forged_signature\n"} +{"Time":"2026-10-18T01:46:03.297625388Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/forged_signature","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/forged_signature (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.297677796Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/forged_signature","Elapsed":0} +{"Time":"2026-10-18T01:46:03.297688626Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unavailable_keys"} +{"Time":"2026-10-18T01:46:03.297695587Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unavailable_keys","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unavailable_keys\n"} +{"Time":"2026-10-18T01:46:03.303157848Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unavailable_keys","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unavailable_keys (0.01s)\n"} +{"Time":"2026-10-18T01:46:03.303209679Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/unavailable_keys","Elapsed":0.01} +{"Time":"2026-10-18T01:46:03.30321903Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_refused"} +{"Time":"2026-10-18T01:46:03.303223548Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_refused","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_refused\n"} +{"Time":"2026-10-18T01:46:03.308075441Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_refused","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_refused (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.308206119Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_refused","Elapsed":0} +{"Time":"2026-10-18T01:46:03.308231403Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_malformed"} +{"Time":"2026-10-18T01:46:03.30824263Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_malformed","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_malformed\n"} +{"Time":"2026-10-18T01:46:03.311516233Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_malformed","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_malformed (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.31155186Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/provider_malformed","Elapsed":0} +{"Time":"2026-10-18T01:46:03.311558623Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/oversize"} +{"Time":"2026-10-18T01:46:03.311562365Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/oversize","Output":"=== RUN TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/oversize\n"} +{"Time":"2026-10-18T01:46:03.336697434Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/oversize","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/oversize (0.02s)\n"} +{"Time":"2026-10-18T01:46:03.336768778Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory/oversize","Elapsed":0.02} +{"Time":"2026-10-18T01:46:03.336782818Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory","Output":"--- PASS: TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory (0.12s)\n"} +{"Time":"2026-10-18T01:46:03.336968329Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeRejectsTokensAndWritesOnlyFailureCategory","Elapsed":0.12} +{"Time":"2026-10-18T01:46:03.337011458Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeBoundsLifetimeAndConfiguration"} +{"Time":"2026-10-18T01:46:03.337027221Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeBoundsLifetimeAndConfiguration","Output":"=== RUN TestIssuerProbeBoundsLifetimeAndConfiguration\n"} +{"Time":"2026-10-18T01:46:03.338748036Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeBoundsLifetimeAndConfiguration","Output":"--- PASS: TestIssuerProbeBoundsLifetimeAndConfiguration (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.338796982Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeBoundsLifetimeAndConfiguration","Elapsed":0} +{"Time":"2026-10-18T01:46:03.338804647Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeErrorsDoNotPrintArguments"} +{"Time":"2026-10-18T01:46:03.338809958Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeErrorsDoNotPrintArguments","Output":"=== RUN TestIssuerProbeErrorsDoNotPrintArguments\n"} +{"Time":"2026-10-18T01:46:03.339007859Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeErrorsDoNotPrintArguments","Output":"--- PASS: TestIssuerProbeErrorsDoNotPrintArguments (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.339022206Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeErrorsDoNotPrintArguments","Elapsed":0} +{"Time":"2026-10-18T01:46:03.339030069Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeCLIReadsOnlyUpstreamAndBoundsIdleRun"} +{"Time":"2026-10-18T01:46:03.33903465Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeCLIReadsOnlyUpstreamAndBoundsIdleRun","Output":"=== RUN TestIssuerProbeCLIReadsOnlyUpstreamAndBoundsIdleRun\n"} +{"Time":"2026-10-18T01:46:03.342246476Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeCLIReadsOnlyUpstreamAndBoundsIdleRun","Output":"--- PASS: TestIssuerProbeCLIReadsOnlyUpstreamAndBoundsIdleRun (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.342311563Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestIssuerProbeCLIReadsOnlyUpstreamAndBoundsIdleRun","Elapsed":0} +{"Time":"2026-10-18T01:46:03.342340977Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenIsAcceptedWhenGenuine"} +{"Time":"2026-10-18T01:46:03.342352321Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenIsAcceptedWhenGenuine","Output":"=== RUN TestUpstreamTokenIsAcceptedWhenGenuine\n"} +{"Time":"2026-10-18T01:46:03.347242718Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenIsAcceptedWhenGenuine","Output":"--- PASS: TestUpstreamTokenIsAcceptedWhenGenuine (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.347303855Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenIsAcceptedWhenGenuine","Elapsed":0} +{"Time":"2026-10-18T01:46:03.34731977Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections"} +{"Time":"2026-10-18T01:46:03.347353459Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections","Output":"=== RUN TestUpstreamTokenRejections\n"} +{"Time":"2026-10-18T01:46:03.481670045Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/forged_signature"} +{"Time":"2026-10-18T01:46:03.481720812Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/forged_signature","Output":"=== RUN TestUpstreamTokenRejections/forged_signature\n"} +{"Time":"2026-10-18T01:46:03.482248411Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/forged_signature","Output":"--- PASS: TestUpstreamTokenRejections/forged_signature (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.48226778Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/forged_signature","Elapsed":0} +{"Time":"2026-10-18T01:46:03.482279169Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unknown_key_id"} +{"Time":"2026-10-18T01:46:03.482285238Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unknown_key_id","Output":"=== RUN TestUpstreamTokenRejections/unknown_key_id\n"} +{"Time":"2026-10-18T01:46:03.482880358Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unknown_key_id","Output":"--- PASS: TestUpstreamTokenRejections/unknown_key_id (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.482898735Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unknown_key_id","Elapsed":0} +{"Time":"2026-10-18T01:46:03.482910449Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/wrong_issuer"} +{"Time":"2026-10-18T01:46:03.482917906Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/wrong_issuer","Output":"=== RUN TestUpstreamTokenRejections/wrong_issuer\n"} +{"Time":"2026-10-18T01:46:03.483508536Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/wrong_issuer","Output":"--- PASS: TestUpstreamTokenRejections/wrong_issuer (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.483527048Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/wrong_issuer","Elapsed":0} +{"Time":"2026-10-18T01:46:03.483534749Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_is_another_client"} +{"Time":"2026-10-18T01:46:03.483540511Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_is_another_client","Output":"=== RUN TestUpstreamTokenRejections/audience_is_another_client\n"} +{"Time":"2026-10-18T01:46:03.484097254Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_is_another_client","Output":"--- PASS: TestUpstreamTokenRejections/audience_is_another_client (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.484116415Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_is_another_client","Elapsed":0} +{"Time":"2026-10-18T01:46:03.484135622Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_array_without_this_client"} +{"Time":"2026-10-18T01:46:03.484241557Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_array_without_this_client","Output":"=== RUN TestUpstreamTokenRejections/audience_array_without_this_client\n"} +{"Time":"2026-10-18T01:46:03.484770031Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_array_without_this_client","Output":"--- PASS: TestUpstreamTokenRejections/audience_array_without_this_client (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.484814314Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/audience_array_without_this_client","Elapsed":0} +{"Time":"2026-10-18T01:46:03.484824747Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/expired"} +{"Time":"2026-10-18T01:46:03.484831077Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/expired","Output":"=== RUN TestUpstreamTokenRejections/expired\n"} +{"Time":"2026-10-18T01:46:03.48550711Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/expired","Output":"--- PASS: TestUpstreamTokenRejections/expired (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.485527137Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/expired","Elapsed":0} +{"Time":"2026-10-18T01:46:03.485534784Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/not_yet_valid"} +{"Time":"2026-10-18T01:46:03.48554087Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/not_yet_valid","Output":"=== RUN TestUpstreamTokenRejections/not_yet_valid\n"} +{"Time":"2026-10-18T01:46:03.486261303Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/not_yet_valid","Output":"--- PASS: TestUpstreamTokenRejections/not_yet_valid (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.486294559Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/not_yet_valid","Elapsed":0} +{"Time":"2026-10-18T01:46:03.486301358Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/undersized_signing_key"} +{"Time":"2026-10-18T01:46:03.486306426Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/undersized_signing_key","Output":"=== RUN TestUpstreamTokenRejections/undersized_signing_key\n"} +{"Time":"2026-10-18T01:46:03.486766296Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/undersized_signing_key","Output":"--- PASS: TestUpstreamTokenRejections/undersized_signing_key (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.486798596Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/undersized_signing_key","Elapsed":0} +{"Time":"2026-10-18T01:46:03.486812447Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/issued_in_the_future"} +{"Time":"2026-10-18T01:46:03.48681911Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/issued_in_the_future","Output":"=== RUN TestUpstreamTokenRejections/issued_in_the_future\n"} +{"Time":"2026-10-18T01:46:03.487563042Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/issued_in_the_future","Output":"--- PASS: TestUpstreamTokenRejections/issued_in_the_future (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.487598643Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/issued_in_the_future","Elapsed":0} +{"Time":"2026-10-18T01:46:03.487607101Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/missing_expiry"} +{"Time":"2026-10-18T01:46:03.487613205Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/missing_expiry","Output":"=== RUN TestUpstreamTokenRejections/missing_expiry\n"} +{"Time":"2026-10-18T01:46:03.48839721Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/missing_expiry","Output":"--- PASS: TestUpstreamTokenRejections/missing_expiry (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.488440231Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/missing_expiry","Elapsed":0} +{"Time":"2026-10-18T01:46:03.488462558Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/malformed_key_set"} +{"Time":"2026-10-18T01:46:03.488468383Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/malformed_key_set","Output":"=== RUN TestUpstreamTokenRejections/malformed_key_set\n"} +{"Time":"2026-10-18T01:46:03.4891251Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/malformed_key_set","Output":"--- PASS: TestUpstreamTokenRejections/malformed_key_set (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.489167518Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/malformed_key_set","Elapsed":0} +{"Time":"2026-10-18T01:46:03.489175245Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/empty_key_set"} +{"Time":"2026-10-18T01:46:03.489180611Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/empty_key_set","Output":"=== RUN TestUpstreamTokenRejections/empty_key_set\n"} +{"Time":"2026-10-18T01:46:03.489373302Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/empty_key_set","Output":"--- PASS: TestUpstreamTokenRejections/empty_key_set (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.489401229Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/empty_key_set","Elapsed":0} +{"Time":"2026-10-18T01:46:03.489411049Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unreachable_key_set"} +{"Time":"2026-10-18T01:46:03.489417586Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unreachable_key_set","Output":"=== RUN TestUpstreamTokenRejections/unreachable_key_set\n"} +{"Time":"2026-10-18T01:46:03.489936547Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unreachable_key_set","Output":"--- PASS: TestUpstreamTokenRejections/unreachable_key_set (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.489987927Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections/unreachable_key_set","Elapsed":0} +{"Time":"2026-10-18T01:46:03.490010921Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections","Output":"--- PASS: TestUpstreamTokenRejections (0.14s)\n"} +{"Time":"2026-10-18T01:46:03.49001827Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejections","Elapsed":0.14} +{"Time":"2026-10-18T01:46:03.490026259Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejectsNonRS256Algorithms"} +{"Time":"2026-10-18T01:46:03.490031363Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejectsNonRS256Algorithms","Output":"=== RUN TestUpstreamTokenRejectsNonRS256Algorithms\n"} +{"Time":"2026-10-18T01:46:03.500278673Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejectsNonRS256Algorithms","Output":"--- PASS: TestUpstreamTokenRejectsNonRS256Algorithms (0.01s)\n"} +{"Time":"2026-10-18T01:46:03.500337141Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamTokenRejectsNonRS256Algorithms","Elapsed":0.01} +{"Time":"2026-10-18T01:46:03.500348368Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamKeyRotationIsPickedUpWithoutRestart"} +{"Time":"2026-10-18T01:46:03.500356564Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamKeyRotationIsPickedUpWithoutRestart","Output":"=== RUN TestUpstreamKeyRotationIsPickedUpWithoutRestart\n"} +{"Time":"2026-10-18T01:46:03.670472014Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamKeyRotationIsPickedUpWithoutRestart","Output":"--- PASS: TestUpstreamKeyRotationIsPickedUpWithoutRestart (0.17s)\n"} +{"Time":"2026-10-18T01:46:03.670529644Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestUpstreamKeyRotationIsPickedUpWithoutRestart","Elapsed":0.17} +{"Time":"2026-10-18T01:46:03.670542295Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable"} +{"Time":"2026-10-18T01:46:03.670591763Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable","Output":"=== RUN TestFailureReasonsAreDistinguishable\n"} +{"Time":"2026-10-18T01:46:03.680832213Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/signature"} +{"Time":"2026-10-18T01:46:03.680882611Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/signature","Output":"=== RUN TestFailureReasonsAreDistinguishable/signature\n"} +{"Time":"2026-10-18T01:46:03.681464674Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/signature","Output":"--- PASS: TestFailureReasonsAreDistinguishable/signature (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.681485576Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/signature","Elapsed":0} +{"Time":"2026-10-18T01:46:03.681494377Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/keys_unavailable"} +{"Time":"2026-10-18T01:46:03.681501284Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/keys_unavailable","Output":"=== RUN TestFailureReasonsAreDistinguishable/keys_unavailable\n"} +{"Time":"2026-10-18T01:46:03.681858268Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/keys_unavailable","Output":"--- PASS: TestFailureReasonsAreDistinguishable/keys_unavailable (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.681890198Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/keys_unavailable","Elapsed":0} +{"Time":"2026-10-18T01:46:03.681899603Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/issuer_mismatch"} +{"Time":"2026-10-18T01:46:03.6819056Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/issuer_mismatch","Output":"=== RUN TestFailureReasonsAreDistinguishable/issuer_mismatch\n"} +{"Time":"2026-10-18T01:46:03.682522307Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/issuer_mismatch","Output":"--- PASS: TestFailureReasonsAreDistinguishable/issuer_mismatch (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.682548701Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/issuer_mismatch","Elapsed":0} +{"Time":"2026-10-18T01:46:03.682575915Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/audience_mismatch"} +{"Time":"2026-10-18T01:46:03.682582128Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/audience_mismatch","Output":"=== RUN TestFailureReasonsAreDistinguishable/audience_mismatch\n"} +{"Time":"2026-10-18T01:46:03.683828439Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/audience_mismatch","Output":"--- PASS: TestFailureReasonsAreDistinguishable/audience_mismatch (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.683868106Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/audience_mismatch","Elapsed":0} +{"Time":"2026-10-18T01:46:03.683889955Z","Action":"run","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/expired"} +{"Time":"2026-10-18T01:46:03.683893661Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/expired","Output":"=== RUN TestFailureReasonsAreDistinguishable/expired\n"} +{"Time":"2026-10-18T01:46:03.685099131Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/expired","Output":"--- PASS: TestFailureReasonsAreDistinguishable/expired (0.00s)\n"} +{"Time":"2026-10-18T01:46:03.685155281Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable/expired","Elapsed":0} +{"Time":"2026-10-18T01:46:03.68516658Z","Action":"output","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable","Output":"--- PASS: TestFailureReasonsAreDistinguishable (0.01s)\n"} +{"Time":"2026-10-18T01:46:03.685377948Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Test":"TestFailureReasonsAreDistinguishable","Elapsed":0.01} +{"Time":"2026-10-18T01:46:03.685401203Z","Action":"output","Package":"keycape/internal/adapters/authelia","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:03.966623532Z","Action":"output","Package":"keycape/cmd/lldap-export","Output":"ok \tkeycape/cmd/lldap-export\t1.032s\n"} +{"Time":"2026-10-18T01:46:03.966701432Z","Action":"pass","Package":"keycape/cmd/lldap-export","Elapsed":1.032} +{"Time":"2026-10-18T01:46:04.002806758Z","Action":"output","Package":"keycape/cmd/keycape","Output":"ok \tkeycape/cmd/keycape\t1.077s\n"} +{"Time":"2026-10-18T01:46:04.002864621Z","Action":"pass","Package":"keycape/cmd/keycape","Elapsed":1.078} +{"Time":"2026-10-18T01:46:04.044979165Z","Action":"output","Package":"keycape/internal/adapters/lldap","Output":"ok \tkeycape/internal/adapters/lldap\t1.026s\n"} +{"Time":"2026-10-18T01:46:04.045035613Z","Action":"pass","Package":"keycape/internal/adapters/lldap","Elapsed":1.026} +{"Time":"2026-10-18T01:46:04.57139712Z","Action":"start","Package":"keycape/internal/adapters/privacyidea"} +{"Time":"2026-10-18T01:46:04.591905722Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_ActiveTokenPresent_ReturnsTrue"} +{"Time":"2026-10-18T01:46:04.591965578Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_ActiveTokenPresent_ReturnsTrue","Output":"=== RUN TestCheckMFARequired_ActiveTokenPresent_ReturnsTrue\n"} +{"Time":"2026-10-18T01:46:04.593004769Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_ActiveTokenPresent_ReturnsTrue","Output":"--- PASS: TestCheckMFARequired_ActiveTokenPresent_ReturnsTrue (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.593054291Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_ActiveTokenPresent_ReturnsTrue","Elapsed":0} +{"Time":"2026-10-18T01:46:04.593070586Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_RequireForAll_ReturnsTrueWithoutTokenList"} +{"Time":"2026-10-18T01:46:04.593077646Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_RequireForAll_ReturnsTrueWithoutTokenList","Output":"=== RUN TestCheckMFARequired_RequireForAll_ReturnsTrueWithoutTokenList\n"} +{"Time":"2026-10-18T01:46:04.593215811Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_RequireForAll_ReturnsTrueWithoutTokenList","Output":"--- PASS: TestCheckMFARequired_RequireForAll_ReturnsTrueWithoutTokenList (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.593236091Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_RequireForAll_ReturnsTrueWithoutTokenList","Elapsed":0} +{"Time":"2026-10-18T01:46:04.593250798Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_InactiveTokenOnly_ReturnsFalse"} +{"Time":"2026-10-18T01:46:04.593279572Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_InactiveTokenOnly_ReturnsFalse","Output":"=== RUN TestCheckMFARequired_InactiveTokenOnly_ReturnsFalse\n"} +{"Time":"2026-10-18T01:46:04.593747847Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_InactiveTokenOnly_ReturnsFalse","Output":"--- PASS: TestCheckMFARequired_InactiveTokenOnly_ReturnsFalse (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.593767035Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_InactiveTokenOnly_ReturnsFalse","Elapsed":0} +{"Time":"2026-10-18T01:46:04.593776188Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_RequireForAllStillListsTokens"} +{"Time":"2026-10-18T01:46:04.59378167Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_RequireForAllStillListsTokens","Output":"=== RUN TestHasEnrolledFactor_RequireForAllStillListsTokens\n"} +{"Time":"2026-10-18T01:46:04.59426559Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_RequireForAllStillListsTokens","Output":"--- PASS: TestHasEnrolledFactor_RequireForAllStillListsTokens (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.594295843Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_RequireForAllStillListsTokens","Elapsed":0} +{"Time":"2026-10-18T01:46:04.594308945Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_ActiveToken_ReturnsTrue"} +{"Time":"2026-10-18T01:46:04.594313805Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_ActiveToken_ReturnsTrue","Output":"=== RUN TestHasEnrolledFactor_ActiveToken_ReturnsTrue\n"} +{"Time":"2026-10-18T01:46:04.594716218Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_ActiveToken_ReturnsTrue","Output":"--- PASS: TestHasEnrolledFactor_ActiveToken_ReturnsTrue (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.594734036Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestHasEnrolledFactor_ActiveToken_ReturnsTrue","Elapsed":0} +{"Time":"2026-10-18T01:46:04.594742089Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_NoTokens_ReturnsFalse"} +{"Time":"2026-10-18T01:46:04.594746823Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_NoTokens_ReturnsFalse","Output":"=== RUN TestCheckMFARequired_NoTokens_ReturnsFalse\n"} +{"Time":"2026-10-18T01:46:04.595167071Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_NoTokens_ReturnsFalse","Output":"--- PASS: TestCheckMFARequired_NoTokens_ReturnsFalse (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.595189885Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_NoTokens_ReturnsFalse","Elapsed":0} +{"Time":"2026-10-18T01:46:04.595197099Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_HTTPError_ReturnsError"} +{"Time":"2026-10-18T01:46:04.595201247Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_HTTPError_ReturnsError","Output":"=== RUN TestCheckMFARequired_HTTPError_ReturnsError\n"} +{"Time":"2026-10-18T01:46:04.595459131Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_HTTPError_ReturnsError","Output":"--- PASS: TestCheckMFARequired_HTTPError_ReturnsError (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.59547869Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_HTTPError_ReturnsError","Elapsed":0} +{"Time":"2026-10-18T01:46:04.595490394Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_Non200Status_ReturnsError"} +{"Time":"2026-10-18T01:46:04.595508582Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_Non200Status_ReturnsError","Output":"=== RUN TestCheckMFARequired_Non200Status_ReturnsError\n"} +{"Time":"2026-10-18T01:46:04.595731073Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_Non200Status_ReturnsError","Output":"--- PASS: TestCheckMFARequired_Non200Status_ReturnsError (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.595745735Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_Non200Status_ReturnsError","Elapsed":0} +{"Time":"2026-10-18T01:46:04.595858965Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_SendsAdminToken"} +{"Time":"2026-10-18T01:46:04.595869317Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_SendsAdminToken","Output":"=== RUN TestCheckMFARequired_SendsAdminToken\n"} +{"Time":"2026-10-18T01:46:04.596391911Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_SendsAdminToken","Output":"--- PASS: TestCheckMFARequired_SendsAdminToken (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.59643266Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_SendsAdminToken","Elapsed":0} +{"Time":"2026-10-18T01:46:04.596450055Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_ValidOTP_ReturnsNil"} +{"Time":"2026-10-18T01:46:04.596454924Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_ValidOTP_ReturnsNil","Output":"=== RUN TestValidateMFAToken_ValidOTP_ReturnsNil\n"} +{"Time":"2026-10-18T01:46:04.597009257Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_ValidOTP_ReturnsNil","Output":"--- PASS: TestValidateMFAToken_ValidOTP_ReturnsNil (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.59705905Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_ValidOTP_ReturnsNil","Elapsed":0} +{"Time":"2026-10-18T01:46:04.597081783Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_InvalidOTP_ReturnsErrMFAFailed"} +{"Time":"2026-10-18T01:46:04.597090169Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_InvalidOTP_ReturnsErrMFAFailed","Output":"=== RUN TestValidateMFAToken_InvalidOTP_ReturnsErrMFAFailed\n"} +{"Time":"2026-10-18T01:46:04.597455135Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_InvalidOTP_ReturnsErrMFAFailed","Output":"--- PASS: TestValidateMFAToken_InvalidOTP_ReturnsErrMFAFailed (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.597492286Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_InvalidOTP_ReturnsErrMFAFailed","Elapsed":0} +{"Time":"2026-10-18T01:46:04.597506795Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_HTTPError_ReturnsError"} +{"Time":"2026-10-18T01:46:04.597512674Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_HTTPError_ReturnsError","Output":"=== RUN TestValidateMFAToken_HTTPError_ReturnsError\n"} +{"Time":"2026-10-18T01:46:04.59776667Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_HTTPError_ReturnsError","Output":"--- PASS: TestValidateMFAToken_HTTPError_ReturnsError (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.597806905Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_HTTPError_ReturnsError","Elapsed":0} +{"Time":"2026-10-18T01:46:04.597819475Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_Non200Status_ReturnsError"} +{"Time":"2026-10-18T01:46:04.597825751Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_Non200Status_ReturnsError","Output":"=== RUN TestValidateMFAToken_Non200Status_ReturnsError\n"} +{"Time":"2026-10-18T01:46:04.598090569Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_Non200Status_ReturnsError","Output":"--- PASS: TestValidateMFAToken_Non200Status_ReturnsError (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.598120858Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_Non200Status_ReturnsError","Elapsed":0} +{"Time":"2026-10-18T01:46:04.598135583Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_IncludesRealm"} +{"Time":"2026-10-18T01:46:04.598147495Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_IncludesRealm","Output":"=== RUN TestValidateMFAToken_IncludesRealm\n"} +{"Time":"2026-10-18T01:46:04.598530142Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_IncludesRealm","Output":"--- PASS: TestValidateMFAToken_IncludesRealm (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.5985754Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestValidateMFAToken_IncludesRealm","Elapsed":0} +{"Time":"2026-10-18T01:46:04.598583705Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_IncludesRealm"} +{"Time":"2026-10-18T01:46:04.598588285Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_IncludesRealm","Output":"=== RUN TestCheckMFARequired_IncludesRealm\n"} +{"Time":"2026-10-18T01:46:04.599372473Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_IncludesRealm","Output":"--- PASS: TestCheckMFARequired_IncludesRealm (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.59941626Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCheckMFARequired_IncludesRealm","Elapsed":0} +{"Time":"2026-10-18T01:46:04.599424358Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialFileRenewalAndFailureRecovery"} +{"Time":"2026-10-18T01:46:04.599429426Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialFileRenewalAndFailureRecovery","Output":"=== RUN TestCredentialFileRenewalAndFailureRecovery\n"} +{"Time":"2026-10-18T01:46:04.60130823Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialFileRenewalAndFailureRecovery","Output":"--- PASS: TestCredentialFileRenewalAndFailureRecovery (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.601364651Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialFileRenewalAndFailureRecovery","Elapsed":0} +{"Time":"2026-10-18T01:46:04.601376148Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues"} +{"Time":"2026-10-18T01:46:04.601380531Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues\n"} +{"Time":"2026-10-18T01:46:04.601755639Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/ambiguous"} +{"Time":"2026-10-18T01:46:04.601777037Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/ambiguous","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues/ambiguous\n"} +{"Time":"2026-10-18T01:46:04.602096762Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/ambiguous","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues/ambiguous (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.602135365Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/ambiguous","Elapsed":0} +{"Time":"2026-10-18T01:46:04.60214954Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/empty"} +{"Time":"2026-10-18T01:46:04.602154166Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/empty","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues/empty\n"} +{"Time":"2026-10-18T01:46:04.602498895Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/empty","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues/empty (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.602516073Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/empty","Elapsed":0} +{"Time":"2026-10-18T01:46:04.602587859Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/newline"} +{"Time":"2026-10-18T01:46:04.602601385Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/newline","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues/newline\n"} +{"Time":"2026-10-18T01:46:04.602973761Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/newline","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues/newline (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.602989145Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/newline","Elapsed":0} +{"Time":"2026-10-18T01:46:04.603070264Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/oversized"} +{"Time":"2026-10-18T01:46:04.603080499Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/oversized","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues/oversized\n"} +{"Time":"2026-10-18T01:46:04.603908381Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/oversized","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues/oversized (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.603942484Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/oversized","Elapsed":0} +{"Time":"2026-10-18T01:46:04.603982386Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/missing"} +{"Time":"2026-10-18T01:46:04.60398868Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/missing","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues/missing\n"} +{"Time":"2026-10-18T01:46:04.604411013Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/missing","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues/missing (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.604450138Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/missing","Elapsed":0} +{"Time":"2026-10-18T01:46:04.604457767Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/directory"} +{"Time":"2026-10-18T01:46:04.604476893Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/directory","Output":"=== RUN TestCredentialSourceErrorsNeverReachProviderOrExposeValues/directory\n"} +{"Time":"2026-10-18T01:46:04.604652458Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/directory","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues/directory (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.604673919Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues/directory","Elapsed":0} +{"Time":"2026-10-18T01:46:04.605057247Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues","Output":"--- PASS: TestCredentialSourceErrorsNeverReachProviderOrExposeValues (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.605118429Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestCredentialSourceErrorsNeverReachProviderOrExposeValues","Elapsed":0} +{"Time":"2026-10-18T01:46:04.605127825Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestProviderFailureFlagCannotGrantAAL2"} +{"Time":"2026-10-18T01:46:04.605134317Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestProviderFailureFlagCannotGrantAAL2","Output":"=== RUN TestProviderFailureFlagCannotGrantAAL2\n"} +{"Time":"2026-10-18T01:46:04.605733216Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestProviderFailureFlagCannotGrantAAL2","Output":"--- PASS: TestProviderFailureFlagCannotGrantAAL2 (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.605774587Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestProviderFailureFlagCannotGrantAAL2","Elapsed":0} +{"Time":"2026-10-18T01:46:04.605786788Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestProductionClientDoesNotFollowProviderRedirects"} +{"Time":"2026-10-18T01:46:04.605793668Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestProductionClientDoesNotFollowProviderRedirects","Output":"=== RUN TestProductionClientDoesNotFollowProviderRedirects\n"} +{"Time":"2026-10-18T01:46:04.613204542Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestProductionClientDoesNotFollowProviderRedirects","Output":"--- PASS: TestProductionClientDoesNotFollowProviderRedirects (0.01s)\n"} +{"Time":"2026-10-18T01:46:04.61330926Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestProductionClientDoesNotFollowProviderRedirects","Elapsed":0.01} +{"Time":"2026-10-18T01:46:04.613328421Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestRejectedCredentialCanRecoverAfterMountedSymlinkRenewal"} +{"Time":"2026-10-18T01:46:04.613336167Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestRejectedCredentialCanRecoverAfterMountedSymlinkRenewal","Output":"=== RUN TestRejectedCredentialCanRecoverAfterMountedSymlinkRenewal\n"} +{"Time":"2026-10-18T01:46:04.614973088Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestRejectedCredentialCanRecoverAfterMountedSymlinkRenewal","Output":"--- PASS: TestRejectedCredentialCanRecoverAfterMountedSymlinkRenewal (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.615024469Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestRejectedCredentialCanRecoverAfterMountedSymlinkRenewal","Elapsed":0} +{"Time":"2026-10-18T01:46:04.615034478Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation"} +{"Time":"2026-10-18T01:46:04.615039537Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation\n"} +{"Time":"2026-10-18T01:46:04.615197248Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/confirmed"} +{"Time":"2026-10-18T01:46:04.615209512Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/confirmed","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/confirmed\n"} +{"Time":"2026-10-18T01:46:04.615529868Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/confirmed","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/confirmed (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.615676795Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/confirmed","Elapsed":0} +{"Time":"2026-10-18T01:46:04.615694559Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/legacy_completed"} +{"Time":"2026-10-18T01:46:04.615699094Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/legacy_completed","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/legacy_completed\n"} +{"Time":"2026-10-18T01:46:04.616087593Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/legacy_completed","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/legacy_completed (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.616127765Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/legacy_completed","Elapsed":0} +{"Time":"2026-10-18T01:46:04.616320707Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_OTP"} +{"Time":"2026-10-18T01:46:04.616336641Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_OTP","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/awaiting_OTP\n"} +{"Time":"2026-10-18T01:46:04.616784942Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_OTP","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/awaiting_OTP (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.616814212Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_OTP","Elapsed":0} +{"Time":"2026-10-18T01:46:04.61690709Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_device"} +{"Time":"2026-10-18T01:46:04.616918235Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_device","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/awaiting_device\n"} +{"Time":"2026-10-18T01:46:04.617403787Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_device","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/awaiting_device (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.617421254Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/awaiting_device","Elapsed":0} +{"Time":"2026-10-18T01:46:04.617515376Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/backend_pending"} +{"Time":"2026-10-18T01:46:04.617525493Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/backend_pending","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/backend_pending\n"} +{"Time":"2026-10-18T01:46:04.618029701Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/backend_pending","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/backend_pending (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.61805623Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/backend_pending","Elapsed":0} +{"Time":"2026-10-18T01:46:04.618141651Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/missing_state"} +{"Time":"2026-10-18T01:46:04.618151813Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/missing_state","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/missing_state\n"} +{"Time":"2026-10-18T01:46:04.618495154Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/missing_state","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/missing_state (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.618521728Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/missing_state","Elapsed":0} +{"Time":"2026-10-18T01:46:04.618611467Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/unknown_state"} +{"Time":"2026-10-18T01:46:04.618622162Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/unknown_state","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/unknown_state\n"} +{"Time":"2026-10-18T01:46:04.619160371Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/unknown_state","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/unknown_state (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.619203744Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/unknown_state","Elapsed":0} +{"Time":"2026-10-18T01:46:04.619216224Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/broken_enrollment"} +{"Time":"2026-10-18T01:46:04.619222572Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/broken_enrollment","Output":"=== RUN TestEnrollmentStateRequiresPossessionConfirmation/broken_enrollment\n"} +{"Time":"2026-10-18T01:46:04.619722949Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/broken_enrollment","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation/broken_enrollment (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.619782869Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation/broken_enrollment","Elapsed":0} +{"Time":"2026-10-18T01:46:04.619803743Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation","Output":"--- PASS: TestEnrollmentStateRequiresPossessionConfirmation (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.619962636Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentStateRequiresPossessionConfirmation","Elapsed":0} +{"Time":"2026-10-18T01:46:04.619975585Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentConfirmationChangesDecisionOnNextLookup"} +{"Time":"2026-10-18T01:46:04.619979544Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentConfirmationChangesDecisionOnNextLookup","Output":"=== RUN TestEnrollmentConfirmationChangesDecisionOnNextLookup\n"} +{"Time":"2026-10-18T01:46:04.621221194Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentConfirmationChangesDecisionOnNextLookup","Output":"--- PASS: TestEnrollmentConfirmationChangesDecisionOnNextLookup (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.621297048Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentConfirmationChangesDecisionOnNextLookup","Elapsed":0} +{"Time":"2026-10-18T01:46:04.621310285Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor"} +{"Time":"2026-10-18T01:46:04.621320979Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor","Output":"=== RUN TestPendingEnrollmentCannotHideExistingFactor\n"} +{"Time":"2026-10-18T01:46:04.621589414Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/existing_verified_factor"} +{"Time":"2026-10-18T01:46:04.621621993Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/existing_verified_factor","Output":"=== RUN TestPendingEnrollmentCannotHideExistingFactor/existing_verified_factor\n"} +{"Time":"2026-10-18T01:46:04.622052636Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/existing_verified_factor","Output":"--- PASS: TestPendingEnrollmentCannotHideExistingFactor/existing_verified_factor (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.622096433Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/existing_verified_factor","Elapsed":0} +{"Time":"2026-10-18T01:46:04.622113873Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/unseen_page_is_uncertain"} +{"Time":"2026-10-18T01:46:04.622118796Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/unseen_page_is_uncertain","Output":"=== RUN TestPendingEnrollmentCannotHideExistingFactor/unseen_page_is_uncertain\n"} +{"Time":"2026-10-18T01:46:04.622562066Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/unseen_page_is_uncertain","Output":"--- PASS: TestPendingEnrollmentCannotHideExistingFactor/unseen_page_is_uncertain (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.622595745Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor/unseen_page_is_uncertain","Elapsed":0} +{"Time":"2026-10-18T01:46:04.622615223Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor","Output":"--- PASS: TestPendingEnrollmentCannotHideExistingFactor (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.6242084Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPendingEnrollmentCannotHideExistingFactor","Elapsed":0} +{"Time":"2026-10-18T01:46:04.624256273Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2"} +{"Time":"2026-10-18T01:46:04.624262233Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2\n"} +{"Time":"2026-10-18T01:46:04.624270791Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/password_passthrough"} +{"Time":"2026-10-18T01:46:04.624275218Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/password_passthrough","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/password_passthrough\n"} +{"Time":"2026-10-18T01:46:04.624296436Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/password_passthrough","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/password_passthrough (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.624302126Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/password_passthrough","Elapsed":0} +{"Time":"2026-10-18T01:46:04.624314344Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/static_password_token"} +{"Time":"2026-10-18T01:46:04.624320846Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/static_password_token","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/static_password_token\n"} +{"Time":"2026-10-18T01:46:04.624327293Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/static_password_token","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/static_password_token (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.624332478Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/static_password_token","Elapsed":0} +{"Time":"2026-10-18T01:46:04.62433723Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_serial"} +{"Time":"2026-10-18T01:46:04.624341342Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_serial","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/missing_serial\n"} +{"Time":"2026-10-18T01:46:04.624795046Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_serial","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/missing_serial (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.624833631Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_serial","Elapsed":0} +{"Time":"2026-10-18T01:46:04.624843803Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/blank_serial"} +{"Time":"2026-10-18T01:46:04.624848483Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/blank_serial","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/blank_serial\n"} +{"Time":"2026-10-18T01:46:04.625177747Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/blank_serial","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/blank_serial (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.625206107Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/blank_serial","Elapsed":0} +{"Time":"2026-10-18T01:46:04.625315901Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_factor_type"} +{"Time":"2026-10-18T01:46:04.625326938Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_factor_type","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/missing_factor_type\n"} +{"Time":"2026-10-18T01:46:04.6255808Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_factor_type","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/missing_factor_type (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.625610025Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/missing_factor_type","Elapsed":0} +{"Time":"2026-10-18T01:46:04.625732182Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/unknown_factor_type"} +{"Time":"2026-10-18T01:46:04.625759243Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/unknown_factor_type","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/unknown_factor_type\n"} +{"Time":"2026-10-18T01:46:04.626031789Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/unknown_factor_type","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/unknown_factor_type (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.626050572Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/unknown_factor_type","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628018746Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/TOTP_confirmed"} +{"Time":"2026-10-18T01:46:04.628064446Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/TOTP_confirmed","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/TOTP_confirmed\n"} +{"Time":"2026-10-18T01:46:04.62807955Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/TOTP_confirmed","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/TOTP_confirmed (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628087169Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/TOTP_confirmed","Elapsed":0} +{"Time":"2026-10-18T01:46:04.62809459Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/HOTP_confirmed"} +{"Time":"2026-10-18T01:46:04.628099225Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/HOTP_confirmed","Output":"=== RUN TestPasswordPassthroughCannotGrantAAL2/HOTP_confirmed\n"} +{"Time":"2026-10-18T01:46:04.628112102Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/HOTP_confirmed","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2/HOTP_confirmed (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628118892Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2/HOTP_confirmed","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628136142Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2","Output":"--- PASS: TestPasswordPassthroughCannotGrantAAL2 (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628178911Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestPasswordPassthroughCannotGrantAAL2","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628185845Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses"} +{"Time":"2026-10-18T01:46:04.62819103Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses\n"} +{"Time":"2026-10-18T01:46:04.628204917Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{}"} +{"Time":"2026-10-18T01:46:04.628209173Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{}\n"} +{"Time":"2026-10-18T01:46:04.628215539Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628220661Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628243339Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[{}],\"count\":1}}}"} +{"Time":"2026-10-18T01:46:04.628249254Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[{}],\"count\":1}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[{}],\"count\":1}}}\n"} +{"Time":"2026-10-18T01:46:04.62825654Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[{}],\"count\":1}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[{}],\"count\":1}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628268398Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[{}],\"count\":1}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628274629Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":false,\"value\":{\"tokens\":[],\"count\":0}}}"} +{"Time":"2026-10-18T01:46:04.628279399Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":false,\"value\":{\"tokens\":[],\"count\":0}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":false,\"value\":{\"tokens\":[],\"count\":0}}}\n"} +{"Time":"2026-10-18T01:46:04.628292483Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":false,\"value\":{\"tokens\":[],\"count\":0}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":false,\"value\":{\"tokens\":[],\"count\":0}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628300815Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":false,\"value\":{\"tokens\":[],\"count\":0}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628330121Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"count\":0}}}"} +{"Time":"2026-10-18T01:46:04.628336316Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"count\":0}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"count\":0}}}\n"} +{"Time":"2026-10-18T01:46:04.628719585Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"count\":0}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"count\":0}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.628760072Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"count\":0}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.628891057Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[]}}}"} +{"Time":"2026-10-18T01:46:04.628908054Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[]}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[]}}}\n"} +{"Time":"2026-10-18T01:46:04.62917675Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[]}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[]}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.629197517Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[]}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.629207932Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":null,\"count\":0}}}"} +{"Time":"2026-10-18T01:46:04.629216787Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":null,\"count\":0}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":null,\"count\":0}}}\n"} +{"Time":"2026-10-18T01:46:04.62951524Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":null,\"count\":0}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":null,\"count\":0}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.629570804Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":null,\"count\":0}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.629691022Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":1}}}"} +{"Time":"2026-10-18T01:46:04.629702393Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":1}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":1}}}\n"} +{"Time":"2026-10-18T01:46:04.629938365Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":1}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":1}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.62996548Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":1}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.63012073Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":-1}}}"} +{"Time":"2026-10-18T01:46:04.630141163Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":-1}}}","Output":"=== RUN TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":-1}}}\n"} +{"Time":"2026-10-18T01:46:04.630418876Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":-1}}}","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":-1}}} (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.630476569Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses/{\"result\":{\"status\":true,\"value\":{\"tokens\":[],\"count\":-1}}}","Elapsed":0} +{"Time":"2026-10-18T01:46:04.630501537Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses","Output":"--- PASS: TestEnrollmentLookupRejectsIndeterminateResponses (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.630538887Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupRejectsIndeterminateResponses","Elapsed":0} +{"Time":"2026-10-18T01:46:04.630551493Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupFiltersBeforePagination"} +{"Time":"2026-10-18T01:46:04.630558337Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupFiltersBeforePagination","Output":"=== RUN TestEnrollmentLookupFiltersBeforePagination\n"} +{"Time":"2026-10-18T01:46:04.63093276Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupFiltersBeforePagination","Output":"--- PASS: TestEnrollmentLookupFiltersBeforePagination (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.630967026Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEnrollmentLookupFiltersBeforePagination","Elapsed":0} +{"Time":"2026-10-18T01:46:04.630984231Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope"} +{"Time":"2026-10-18T01:46:04.630990624Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope","Output":"=== RUN TestEmptyFactorListRequiresLiveReaderScope\n"} +{"Time":"2026-10-18T01:46:04.631000218Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/scope_visible"} +{"Time":"2026-10-18T01:46:04.631006016Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/scope_visible","Output":"=== RUN TestEmptyFactorListRequiresLiveReaderScope/scope_visible\n"} +{"Time":"2026-10-18T01:46:04.631422117Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/scope_visible","Output":"--- PASS: TestEmptyFactorListRequiresLiveReaderScope/scope_visible (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.631454435Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/scope_visible","Elapsed":0} +{"Time":"2026-10-18T01:46:04.631477122Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/permission_withdrawn"} +{"Time":"2026-10-18T01:46:04.631484868Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/permission_withdrawn","Output":"=== RUN TestEmptyFactorListRequiresLiveReaderScope/permission_withdrawn\n"} +{"Time":"2026-10-18T01:46:04.63189683Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/permission_withdrawn","Output":"--- PASS: TestEmptyFactorListRequiresLiveReaderScope/permission_withdrawn (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.631930194Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/permission_withdrawn","Elapsed":0} +{"Time":"2026-10-18T01:46:04.631944874Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/probe_assigned_to_a_user"} +{"Time":"2026-10-18T01:46:04.632070079Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/probe_assigned_to_a_user","Output":"=== RUN TestEmptyFactorListRequiresLiveReaderScope/probe_assigned_to_a_user\n"} +{"Time":"2026-10-18T01:46:04.632403897Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/probe_assigned_to_a_user","Output":"--- PASS: TestEmptyFactorListRequiresLiveReaderScope/probe_assigned_to_a_user (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.632451094Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/probe_assigned_to_a_user","Elapsed":0} +{"Time":"2026-10-18T01:46:04.632461409Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/different_realm"} +{"Time":"2026-10-18T01:46:04.632467325Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/different_realm","Output":"=== RUN TestEmptyFactorListRequiresLiveReaderScope/different_realm\n"} +{"Time":"2026-10-18T01:46:04.632945933Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/different_realm","Output":"--- PASS: TestEmptyFactorListRequiresLiveReaderScope/different_realm (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.632981704Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope/different_realm","Elapsed":0} +{"Time":"2026-10-18T01:46:04.633000551Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope","Output":"--- PASS: TestEmptyFactorListRequiresLiveReaderScope (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.633135566Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestEmptyFactorListRequiresLiveReaderScope","Elapsed":0} +{"Time":"2026-10-18T01:46:04.633149119Z","Action":"run","Package":"keycape/internal/adapters/privacyidea","Test":"TestNoProbeConfigurationCannotMeanNoFactor"} +{"Time":"2026-10-18T01:46:04.633154863Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestNoProbeConfigurationCannotMeanNoFactor","Output":"=== RUN TestNoProbeConfigurationCannotMeanNoFactor\n"} +{"Time":"2026-10-18T01:46:04.633483938Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Test":"TestNoProbeConfigurationCannotMeanNoFactor","Output":"--- PASS: TestNoProbeConfigurationCannotMeanNoFactor (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.633512631Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Test":"TestNoProbeConfigurationCannotMeanNoFactor","Elapsed":0} +{"Time":"2026-10-18T01:46:04.63352218Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:04.670367697Z","Action":"start","Package":"keycape/internal/adapters/tenantengine"} +{"Time":"2026-10-18T01:46:04.691642882Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesReturnsRolesOnSuccess"} +{"Time":"2026-10-18T01:46:04.691690105Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesReturnsRolesOnSuccess","Output":"=== RUN TestRolesReturnsRolesOnSuccess\n"} +{"Time":"2026-10-18T01:46:04.693443032Z","Action":"output","Package":"keycape/internal/adapters/authelia","Output":"ok \tkeycape/internal/adapters/authelia\t1.701s\n"} +{"Time":"2026-10-18T01:46:04.693497334Z","Action":"pass","Package":"keycape/internal/adapters/authelia","Elapsed":1.701} +{"Time":"2026-10-18T01:46:04.696693754Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesReturnsRolesOnSuccess","Output":"--- PASS: TestRolesReturnsRolesOnSuccess (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.696760598Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesReturnsRolesOnSuccess","Elapsed":0} +{"Time":"2026-10-18T01:46:04.696792465Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNon200"} +{"Time":"2026-10-18T01:46:04.696799562Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNon200","Output":"=== RUN TestRolesFailsOpenOnNon200\n"} +{"Time":"2026-10-18T01:46:04.699306178Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNon200","Output":"--- PASS: TestRolesFailsOpenOnNon200 (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.699364177Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNon200","Elapsed":0} +{"Time":"2026-10-18T01:46:04.699380976Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnMalformedBody"} +{"Time":"2026-10-18T01:46:04.699384872Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnMalformedBody","Output":"=== RUN TestRolesFailsOpenOnMalformedBody\n"} +{"Time":"2026-10-18T01:46:04.702375639Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnMalformedBody","Output":"--- PASS: TestRolesFailsOpenOnMalformedBody (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.702431266Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnMalformedBody","Elapsed":0} +{"Time":"2026-10-18T01:46:04.702446622Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnConnectionFailure"} +{"Time":"2026-10-18T01:46:04.702451735Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnConnectionFailure","Output":"=== RUN TestRolesFailsOpenOnConnectionFailure\n"} +{"Time":"2026-10-18T01:46:04.70362013Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnConnectionFailure","Output":"--- PASS: TestRolesFailsOpenOnConnectionFailure (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.703798939Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnConnectionFailure","Elapsed":0} +{"Time":"2026-10-18T01:46:04.703831257Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnTimeout"} +{"Time":"2026-10-18T01:46:04.703844467Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnTimeout","Output":"=== RUN TestRolesFailsOpenOnTimeout\n"} +{"Time":"2026-10-18T01:46:04.806996957Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnTimeout","Output":"--- PASS: TestRolesFailsOpenOnTimeout (0.10s)\n"} +{"Time":"2026-10-18T01:46:04.807069898Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnTimeout","Elapsed":0.1} +{"Time":"2026-10-18T01:46:04.807098365Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNilClient"} +{"Time":"2026-10-18T01:46:04.8071074Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNilClient","Output":"=== RUN TestRolesFailsOpenOnNilClient\n"} +{"Time":"2026-10-18T01:46:04.807345288Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNilClient","Output":"--- PASS: TestRolesFailsOpenOnNilClient (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.807446588Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnNilClient","Elapsed":0} +{"Time":"2026-10-18T01:46:04.807629355Z","Action":"run","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnEmptyBaseURL"} +{"Time":"2026-10-18T01:46:04.807660708Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnEmptyBaseURL","Output":"=== RUN TestRolesFailsOpenOnEmptyBaseURL\n"} +{"Time":"2026-10-18T01:46:04.808023505Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnEmptyBaseURL","Output":"--- PASS: TestRolesFailsOpenOnEmptyBaseURL (0.00s)\n"} +{"Time":"2026-10-18T01:46:04.808078573Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Test":"TestRolesFailsOpenOnEmptyBaseURL","Elapsed":0} +{"Time":"2026-10-18T01:46:04.808328766Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:05.137669468Z","Action":"start","Package":"keycape/internal/authclient"} +{"Time":"2026-10-18T01:46:05.152734363Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestServiceExchangeAndClaimValidation"} +{"Time":"2026-10-18T01:46:05.152782172Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestServiceExchangeAndClaimValidation","Output":"=== RUN TestServiceExchangeAndClaimValidation\n"} +{"Time":"2026-10-18T01:46:05.245703242Z","Action":"start","Package":"keycape/internal/config"} +{"Time":"2026-10-18T01:46:05.262352274Z","Action":"run","Package":"keycape/internal/config","Test":"TestAccountRecoveryDestinationsAreOwnerConfiguredHTTPS"} +{"Time":"2026-10-18T01:46:05.262415457Z","Action":"output","Package":"keycape/internal/config","Test":"TestAccountRecoveryDestinationsAreOwnerConfiguredHTTPS","Output":"=== RUN TestAccountRecoveryDestinationsAreOwnerConfiguredHTTPS\n"} +{"Time":"2026-10-18T01:46:05.262692716Z","Action":"output","Package":"keycape/internal/config","Test":"TestAccountRecoveryDestinationsAreOwnerConfiguredHTTPS","Output":"--- PASS: TestAccountRecoveryDestinationsAreOwnerConfiguredHTTPS (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.262712554Z","Action":"pass","Package":"keycape/internal/config","Test":"TestAccountRecoveryDestinationsAreOwnerConfiguredHTTPS","Elapsed":0} +{"Time":"2026-10-18T01:46:05.262790156Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_ValidYAML"} +{"Time":"2026-10-18T01:46:05.26280068Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_ValidYAML","Output":"=== RUN TestLoad_ValidYAML\n"} +{"Time":"2026-10-18T01:46:05.264377854Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_ValidYAML","Output":"--- PASS: TestLoad_ValidYAML (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.264438667Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_ValidYAML","Elapsed":0} +{"Time":"2026-10-18T01:46:05.264457648Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_AutheliaSplitURLs"} +{"Time":"2026-10-18T01:46:05.264465682Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_AutheliaSplitURLs","Output":"=== RUN TestLoad_AutheliaSplitURLs\n"} +{"Time":"2026-10-18T01:46:05.265396072Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestServiceExchangeAndClaimValidation","Output":"--- PASS: TestServiceExchangeAndClaimValidation (0.11s)\n"} +{"Time":"2026-10-18T01:46:05.265436379Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestServiceExchangeAndClaimValidation","Elapsed":0.11} +{"Time":"2026-10-18T01:46:05.265576651Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestBrowserLoginPKCEAndState"} +{"Time":"2026-10-18T01:46:05.265597905Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestBrowserLoginPKCEAndState","Output":"=== RUN TestBrowserLoginPKCEAndState\n"} +{"Time":"2026-10-18T01:46:05.266386175Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_AutheliaSplitURLs","Output":"--- PASS: TestLoad_AutheliaSplitURLs (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.266431532Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_AutheliaSplitURLs","Elapsed":0} +{"Time":"2026-10-18T01:46:05.266438565Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_ClientMFAAndRegistrationURL"} +{"Time":"2026-10-18T01:46:05.266442722Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_ClientMFAAndRegistrationURL","Output":"=== RUN TestLoad_ClientMFAAndRegistrationURL\n"} +{"Time":"2026-10-18T01:46:05.268209589Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_ClientMFAAndRegistrationURL","Output":"--- PASS: TestLoad_ClientMFAAndRegistrationURL (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.268254864Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_ClientMFAAndRegistrationURL","Elapsed":0} +{"Time":"2026-10-18T01:46:05.268263909Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_InvalidRegistrationURL"} +{"Time":"2026-10-18T01:46:05.268268444Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_InvalidRegistrationURL","Output":"=== RUN TestValidate_InvalidRegistrationURL\n"} +{"Time":"2026-10-18T01:46:05.268666835Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_InvalidRegistrationURL","Output":"--- PASS: TestValidate_InvalidRegistrationURL (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.268681272Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_InvalidRegistrationURL","Elapsed":0} +{"Time":"2026-10-18T01:46:05.26876038Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_PrivacyIDEARequireForAll"} +{"Time":"2026-10-18T01:46:05.268771201Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_PrivacyIDEARequireForAll","Output":"=== RUN TestLoad_PrivacyIDEARequireForAll\n"} +{"Time":"2026-10-18T01:46:05.270212356Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_PrivacyIDEARequireForAll","Output":"--- PASS: TestLoad_PrivacyIDEARequireForAll (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.270256567Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_PrivacyIDEARequireForAll","Elapsed":0} +{"Time":"2026-10-18T01:46:05.270274575Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_LLDAPOrganisationalUnits"} +{"Time":"2026-10-18T01:46:05.270278993Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_LLDAPOrganisationalUnits","Output":"=== RUN TestLoad_LLDAPOrganisationalUnits\n"} +{"Time":"2026-10-18T01:46:05.271714793Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_LLDAPOrganisationalUnits","Output":"--- PASS: TestLoad_LLDAPOrganisationalUnits (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.271803451Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_LLDAPOrganisationalUnits","Elapsed":0} +{"Time":"2026-10-18T01:46:05.271817139Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_FileNotFound"} +{"Time":"2026-10-18T01:46:05.271822405Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_FileNotFound","Output":"=== RUN TestLoad_FileNotFound\n"} +{"Time":"2026-10-18T01:46:05.272257312Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_FileNotFound","Output":"--- PASS: TestLoad_FileNotFound (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.272296456Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_FileNotFound","Elapsed":0} +{"Time":"2026-10-18T01:46:05.272311461Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_InvalidYAML"} +{"Time":"2026-10-18T01:46:05.272320081Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_InvalidYAML","Output":"=== RUN TestLoad_InvalidYAML\n"} +{"Time":"2026-10-18T01:46:05.272908397Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_InvalidYAML","Output":"--- PASS: TestLoad_InvalidYAML (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.273173712Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_InvalidYAML","Elapsed":0} +{"Time":"2026-10-18T01:46:05.273194506Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ValidConfig"} +{"Time":"2026-10-18T01:46:05.273201052Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ValidConfig","Output":"=== RUN TestValidate_ValidConfig\n"} +{"Time":"2026-10-18T01:46:05.273685459Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ValidConfig","Output":"--- PASS: TestValidate_ValidConfig (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.273720518Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ValidConfig","Elapsed":0} +{"Time":"2026-10-18T01:46:05.273730076Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_MissingIssuer"} +{"Time":"2026-10-18T01:46:05.273735017Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_MissingIssuer","Output":"=== RUN TestValidate_MissingIssuer\n"} +{"Time":"2026-10-18T01:46:05.27416413Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_MissingIssuer","Output":"--- PASS: TestValidate_MissingIssuer (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.274179802Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_MissingIssuer","Elapsed":0} +{"Time":"2026-10-18T01:46:05.274269812Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_InvalidIssuerURL"} +{"Time":"2026-10-18T01:46:05.274282175Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_InvalidIssuerURL","Output":"=== RUN TestValidate_InvalidIssuerURL\n"} +{"Time":"2026-10-18T01:46:05.274658221Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_InvalidIssuerURL","Output":"--- PASS: TestValidate_InvalidIssuerURL (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.274675697Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_InvalidIssuerURL","Elapsed":0} +{"Time":"2026-10-18T01:46:05.27470319Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_PortZero"} +{"Time":"2026-10-18T01:46:05.274707762Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_PortZero","Output":"=== RUN TestValidate_PortZero\n"} +{"Time":"2026-10-18T01:46:05.275142204Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_PortZero","Output":"--- PASS: TestValidate_PortZero (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.275155072Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_PortZero","Elapsed":0} +{"Time":"2026-10-18T01:46:05.275171781Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_PortTooHigh"} +{"Time":"2026-10-18T01:46:05.275177705Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_PortTooHigh","Output":"=== RUN TestValidate_PortTooHigh\n"} +{"Time":"2026-10-18T01:46:05.275524021Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_PortTooHigh","Output":"--- PASS: TestValidate_PortTooHigh (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.27553642Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_PortTooHigh","Elapsed":0} +{"Time":"2026-10-18T01:46:05.275547349Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_NoClients"} +{"Time":"2026-10-18T01:46:05.275551317Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_NoClients","Output":"=== RUN TestValidate_NoClients\n"} +{"Time":"2026-10-18T01:46:05.276253295Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_NoClients","Output":"--- PASS: TestValidate_NoClients (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.276303954Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_NoClients","Elapsed":0} +{"Time":"2026-10-18T01:46:05.276311672Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ClientMissingRedirectURI"} +{"Time":"2026-10-18T01:46:05.276315784Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ClientMissingRedirectURI","Output":"=== RUN TestValidate_ClientMissingRedirectURI\n"} +{"Time":"2026-10-18T01:46:05.276979206Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ClientMissingRedirectURI","Output":"--- PASS: TestValidate_ClientMissingRedirectURI (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.277014526Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ClientMissingRedirectURI","Elapsed":0} +{"Time":"2026-10-18T01:46:05.277023345Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_MissingPrivateKeyPEM"} +{"Time":"2026-10-18T01:46:05.277040622Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_MissingPrivateKeyPEM","Output":"=== RUN TestValidate_MissingPrivateKeyPEM\n"} +{"Time":"2026-10-18T01:46:05.277237929Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_MissingPrivateKeyPEM","Output":"--- PASS: TestValidate_MissingPrivateKeyPEM (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.277253691Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_MissingPrivateKeyPEM","Elapsed":0} +{"Time":"2026-10-18T01:46:05.277297533Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ClientCredentialsTokenLifetime"} +{"Time":"2026-10-18T01:46:05.277307118Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ClientCredentialsTokenLifetime","Output":"=== RUN TestValidate_ClientCredentialsTokenLifetime\n"} +{"Time":"2026-10-18T01:46:05.277807701Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ClientCredentialsTokenLifetime","Output":"--- PASS: TestValidate_ClientCredentialsTokenLifetime (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.277828576Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ClientCredentialsTokenLifetime","Elapsed":0} +{"Time":"2026-10-18T01:46:05.277836358Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_PublicClientRejectsTokenLifetime"} +{"Time":"2026-10-18T01:46:05.277840299Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_PublicClientRejectsTokenLifetime","Output":"=== RUN TestValidate_PublicClientRejectsTokenLifetime\n"} +{"Time":"2026-10-18T01:46:05.27825236Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_PublicClientRejectsTokenLifetime","Output":"--- PASS: TestValidate_PublicClientRejectsTokenLifetime (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.278272045Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_PublicClientRejectsTokenLifetime","Elapsed":0} +{"Time":"2026-10-18T01:46:05.27828071Z","Action":"run","Package":"keycape/internal/config","Test":"TestServiceClientExampleContracts"} +{"Time":"2026-10-18T01:46:05.278285039Z","Action":"output","Package":"keycape/internal/config","Test":"TestServiceClientExampleContracts","Output":"=== RUN TestServiceClientExampleContracts\n"} +{"Time":"2026-10-18T01:46:05.284084771Z","Action":"output","Package":"keycape/internal/config","Test":"TestServiceClientExampleContracts","Output":"--- PASS: TestServiceClientExampleContracts (0.01s)\n"} +{"Time":"2026-10-18T01:46:05.284230877Z","Action":"pass","Package":"keycape/internal/config","Test":"TestServiceClientExampleContracts","Elapsed":0.01} +{"Time":"2026-10-18T01:46:05.284247027Z","Action":"run","Package":"keycape/internal/config","Test":"TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback"} +{"Time":"2026-10-18T01:46:05.284252645Z","Action":"output","Package":"keycape/internal/config","Test":"TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback","Output":"=== RUN TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback\n"} +{"Time":"2026-10-18T01:46:05.287568181Z","Action":"output","Package":"keycape/internal/config","Test":"TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback","Output":"--- PASS: TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.287610354Z","Action":"pass","Package":"keycape/internal/config","Test":"TestDevConfigOpenBaoAdminAdmitsOperatorTunnelCallback","Elapsed":0} +{"Time":"2026-10-18T01:46:05.287620787Z","Action":"run","Package":"keycape/internal/config","Test":"TestLoad_FromEnvVar"} +{"Time":"2026-10-18T01:46:05.287626216Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_FromEnvVar","Output":"=== RUN TestLoad_FromEnvVar\n"} +{"Time":"2026-10-18T01:46:05.289124414Z","Action":"output","Package":"keycape/internal/config","Test":"TestLoad_FromEnvVar","Output":"--- PASS: TestLoad_FromEnvVar (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.289184045Z","Action":"pass","Package":"keycape/internal/config","Test":"TestLoad_FromEnvVar","Elapsed":0} +{"Time":"2026-10-18T01:46:05.289192115Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidateConfigAudience"} +{"Time":"2026-10-18T01:46:05.289201277Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidateConfigAudience","Output":"=== RUN TestValidateConfigAudience\n"} +{"Time":"2026-10-18T01:46:05.289517271Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidateConfigAudience","Output":"--- PASS: TestValidateConfigAudience (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.289558038Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidateConfigAudience","Elapsed":0} +{"Time":"2026-10-18T01:46:05.289567452Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine"} +{"Time":"2026-10-18T01:46:05.289571464Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine","Output":"=== RUN TestValidate_TenantEngine\n"} +{"Time":"2026-10-18T01:46:05.289737235Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/absent_is_valid"} +{"Time":"2026-10-18T01:46:05.289752943Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/absent_is_valid","Output":"=== RUN TestValidate_TenantEngine/absent_is_valid\n"} +{"Time":"2026-10-18T01:46:05.290354937Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/absent_is_valid","Output":"--- PASS: TestValidate_TenantEngine/absent_is_valid (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.290389545Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/absent_is_valid","Elapsed":0} +{"Time":"2026-10-18T01:46:05.290397092Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/not_a_url"} +{"Time":"2026-10-18T01:46:05.290401475Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/not_a_url","Output":"=== RUN TestValidate_TenantEngine/not_a_url\n"} +{"Time":"2026-10-18T01:46:05.290935104Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/not_a_url","Output":"--- PASS: TestValidate_TenantEngine/not_a_url (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.290961696Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/not_a_url","Elapsed":0} +{"Time":"2026-10-18T01:46:05.290968522Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/wrong_scheme"} +{"Time":"2026-10-18T01:46:05.290973256Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/wrong_scheme","Output":"=== RUN TestValidate_TenantEngine/wrong_scheme\n"} +{"Time":"2026-10-18T01:46:05.291420602Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/wrong_scheme","Output":"--- PASS: TestValidate_TenantEngine/wrong_scheme (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.291438844Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/wrong_scheme","Elapsed":0} +{"Time":"2026-10-18T01:46:05.291515085Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/unparseable_timeout"} +{"Time":"2026-10-18T01:46:05.291524877Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/unparseable_timeout","Output":"=== RUN TestValidate_TenantEngine/unparseable_timeout\n"} +{"Time":"2026-10-18T01:46:05.291958607Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/unparseable_timeout","Output":"--- PASS: TestValidate_TenantEngine/unparseable_timeout (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.291973936Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/unparseable_timeout","Elapsed":0} +{"Time":"2026-10-18T01:46:05.292070006Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/zero_timeout"} +{"Time":"2026-10-18T01:46:05.292092342Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/zero_timeout","Output":"=== RUN TestValidate_TenantEngine/zero_timeout\n"} +{"Time":"2026-10-18T01:46:05.292563511Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/zero_timeout","Output":"--- PASS: TestValidate_TenantEngine/zero_timeout (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.29258875Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/zero_timeout","Elapsed":0} +{"Time":"2026-10-18T01:46:05.292603385Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_far_too_long"} +{"Time":"2026-10-18T01:46:05.292607614Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_far_too_long","Output":"=== RUN TestValidate_TenantEngine/timeout_far_too_long\n"} +{"Time":"2026-10-18T01:46:05.29306221Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_far_too_long","Output":"--- PASS: TestValidate_TenantEngine/timeout_far_too_long (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.29307643Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_far_too_long","Elapsed":0} +{"Time":"2026-10-18T01:46:05.293154799Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/http_url"} +{"Time":"2026-10-18T01:46:05.293164763Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/http_url","Output":"=== RUN TestValidate_TenantEngine/http_url\n"} +{"Time":"2026-10-18T01:46:05.293612361Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/http_url","Output":"--- PASS: TestValidate_TenantEngine/http_url (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.29362577Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/http_url","Elapsed":0} +{"Time":"2026-10-18T01:46:05.293714978Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/https_url_and_timeout"} +{"Time":"2026-10-18T01:46:05.293724698Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/https_url_and_timeout","Output":"=== RUN TestValidate_TenantEngine/https_url_and_timeout\n"} +{"Time":"2026-10-18T01:46:05.294182459Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/https_url_and_timeout","Output":"--- PASS: TestValidate_TenantEngine/https_url_and_timeout (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.294196237Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/https_url_and_timeout","Elapsed":0} +{"Time":"2026-10-18T01:46:05.29428953Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_without_url"} +{"Time":"2026-10-18T01:46:05.294299304Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_without_url","Output":"=== RUN TestValidate_TenantEngine/timeout_without_url\n"} +{"Time":"2026-10-18T01:46:05.294800753Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_without_url","Output":"--- PASS: TestValidate_TenantEngine/timeout_without_url (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.294819464Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine/timeout_without_url","Elapsed":0} +{"Time":"2026-10-18T01:46:05.294934436Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine","Output":"--- PASS: TestValidate_TenantEngine (0.01s)\n"} +{"Time":"2026-10-18T01:46:05.295037222Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_TenantEngine","Elapsed":0.01} +{"Time":"2026-10-18T01:46:05.295052785Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients"} +{"Time":"2026-10-18T01:46:05.295070649Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients","Output":"=== RUN TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients\n"} +{"Time":"2026-10-18T01:46:05.295231237Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/serviceSubject"} +{"Time":"2026-10-18T01:46:05.295242553Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/serviceSubject","Output":"=== RUN TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/serviceSubject\n"} +{"Time":"2026-10-18T01:46:05.295682307Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/serviceSubject","Output":"--- PASS: TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/serviceSubject (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.295696229Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/serviceSubject","Elapsed":0} +{"Time":"2026-10-18T01:46:05.295769648Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/roles"} +{"Time":"2026-10-18T01:46:05.295786068Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/roles","Output":"=== RUN TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/roles\n"} +{"Time":"2026-10-18T01:46:05.296451397Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/roles","Output":"--- PASS: TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/roles (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.296547467Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/roles","Elapsed":0} +{"Time":"2026-10-18T01:46:05.296568098Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/implicit_authorization_code_client"} +{"Time":"2026-10-18T01:46:05.296573454Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/implicit_authorization_code_client","Output":"=== RUN TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/implicit_authorization_code_client\n"} +{"Time":"2026-10-18T01:46:05.297159079Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/implicit_authorization_code_client","Output":"--- PASS: TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/implicit_authorization_code_client (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.297178674Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/implicit_authorization_code_client","Elapsed":0} +{"Time":"2026-10-18T01:46:05.297271705Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/tenant_is_allowed_on_a_browser_client"} +{"Time":"2026-10-18T01:46:05.297282661Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/tenant_is_allowed_on_a_browser_client","Output":"=== RUN TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/tenant_is_allowed_on_a_browser_client\n"} +{"Time":"2026-10-18T01:46:05.297752712Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/tenant_is_allowed_on_a_browser_client","Output":"--- PASS: TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/tenant_is_allowed_on_a_browser_client (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.297770079Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/tenant_is_allowed_on_a_browser_client","Elapsed":0} +{"Time":"2026-10-18T01:46:05.297861334Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/service_clients_unaffected"} +{"Time":"2026-10-18T01:46:05.297872073Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/service_clients_unaffected","Output":"=== RUN TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/service_clients_unaffected\n"} +{"Time":"2026-10-18T01:46:05.298346354Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/service_clients_unaffected","Output":"--- PASS: TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/service_clients_unaffected (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.298360393Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients/service_clients_unaffected","Elapsed":0} +{"Time":"2026-10-18T01:46:05.298450584Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients","Output":"--- PASS: TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.298547736Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_ServiceIdentityFieldsRejectedOnBrowserClients","Elapsed":0} +{"Time":"2026-10-18T01:46:05.298565437Z","Action":"run","Package":"keycape/internal/config","Test":"TestValidate_DuplicateClientID"} +{"Time":"2026-10-18T01:46:05.298570035Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_DuplicateClientID","Output":"=== RUN TestValidate_DuplicateClientID\n"} +{"Time":"2026-10-18T01:46:05.299028788Z","Action":"output","Package":"keycape/internal/config","Test":"TestValidate_DuplicateClientID","Output":"--- PASS: TestValidate_DuplicateClientID (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.299042738Z","Action":"pass","Package":"keycape/internal/config","Test":"TestValidate_DuplicateClientID","Elapsed":0} +{"Time":"2026-10-18T01:46:05.299126661Z","Action":"run","Package":"keycape/internal/config","Test":"TestOptionalMFALoadAndRegistration"} +{"Time":"2026-10-18T01:46:05.299136562Z","Action":"output","Package":"keycape/internal/config","Test":"TestOptionalMFALoadAndRegistration","Output":"=== RUN TestOptionalMFALoadAndRegistration\n"} +{"Time":"2026-10-18T01:46:05.299884564Z","Action":"output","Package":"keycape/internal/config","Test":"TestOptionalMFALoadAndRegistration","Output":"--- PASS: TestOptionalMFALoadAndRegistration (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.299921047Z","Action":"pass","Package":"keycape/internal/config","Test":"TestOptionalMFALoadAndRegistration","Elapsed":0} +{"Time":"2026-10-18T01:46:05.299927819Z","Action":"run","Package":"keycape/internal/config","Test":"TestOptionalMFAValidation"} +{"Time":"2026-10-18T01:46:05.299933049Z","Action":"output","Package":"keycape/internal/config","Test":"TestOptionalMFAValidation","Output":"=== RUN TestOptionalMFAValidation\n"} +{"Time":"2026-10-18T01:46:05.300907604Z","Action":"output","Package":"keycape/internal/config","Test":"TestOptionalMFAValidation","Output":"--- PASS: TestOptionalMFAValidation (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.300955224Z","Action":"pass","Package":"keycape/internal/config","Test":"TestOptionalMFAValidation","Elapsed":0} +{"Time":"2026-10-18T01:46:05.300966252Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS"} +{"Time":"2026-10-18T01:46:05.300971879Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS\n"} +{"Time":"2026-10-18T01:46:05.301116753Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy"} +{"Time":"2026-10-18T01:46:05.301140089Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy\n"} +{"Time":"2026-10-18T01:46:05.30133846Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.301355232Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/legacy","Elapsed":0} +{"Time":"2026-10-18T01:46:05.301361391Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default"} +{"Time":"2026-10-18T01:46:05.301365566Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default\n"} +{"Time":"2026-10-18T01:46:05.301493428Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.301514483Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-default","Elapsed":0} +{"Time":"2026-10-18T01:46:05.301543419Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded"} +{"Time":"2026-10-18T01:46:05.301550056Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded\n"} +{"Time":"2026-10-18T01:46:05.301717658Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.301754259Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/enabled-bounded","Elapsed":0} +{"Time":"2026-10-18T01:46:05.30176554Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http"} +{"Time":"2026-10-18T01:46:05.301773619Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http\n"} +{"Time":"2026-10-18T01:46:05.301978907Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.302009286Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/http","Elapsed":0} +{"Time":"2026-10-18T01:46:05.302017213Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url"} +{"Time":"2026-10-18T01:46:05.302021207Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url\n"} +{"Time":"2026-10-18T01:46:05.302236927Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.302254817Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/userinfo-url","Elapsed":0} +{"Time":"2026-10-18T01:46:05.302337668Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query"} +{"Time":"2026-10-18T01:46:05.302346622Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query\n"} +{"Time":"2026-10-18T01:46:05.30259064Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.302628522Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/query","Elapsed":0} +{"Time":"2026-10-18T01:46:05.302637512Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large"} +{"Time":"2026-10-18T01:46:05.302642111Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large\n"} +{"Time":"2026-10-18T01:46:05.302812236Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.30282753Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/too-large","Elapsed":0} +{"Time":"2026-10-18T01:46:05.302914925Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative"} +{"Time":"2026-10-18T01:46:05.302923158Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative\n"} +{"Time":"2026-10-18T01:46:05.303086352Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.303116875Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/negative","Elapsed":0} +{"Time":"2026-10-18T01:46:05.303176551Z","Action":"run","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity"} +{"Time":"2026-10-18T01:46:05.303184432Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Output":"=== RUN TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity\n"} +{"Time":"2026-10-18T01:46:05.303351432Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.303371829Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS/disabled-capacity","Elapsed":0} +{"Time":"2026-10-18T01:46:05.30354569Z","Action":"output","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Output":"--- PASS: TestTokenStatusConfigIsExplicitBoundedAndHTTPS (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.303556872Z","Action":"pass","Package":"keycape/internal/config","Test":"TestTokenStatusConfigIsExplicitBoundedAndHTTPS","Elapsed":0} +{"Time":"2026-10-18T01:46:05.303563094Z","Action":"output","Package":"keycape/internal/config","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:05.333592463Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestBrowserLoginPKCEAndState","Output":"--- PASS: TestBrowserLoginPKCEAndState (0.07s)\n"} +{"Time":"2026-10-18T01:46:05.333636972Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestBrowserLoginPKCEAndState","Elapsed":0.07} +{"Time":"2026-10-18T01:46:05.333712798Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestLoginRejectsUnsafeCallbacks"} +{"Time":"2026-10-18T01:46:05.333732384Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestLoginRejectsUnsafeCallbacks","Output":"=== RUN TestLoginRejectsUnsafeCallbacks\n"} +{"Time":"2026-10-18T01:46:05.473674341Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestLoginRejectsUnsafeCallbacks","Output":"--- PASS: TestLoginRejectsUnsafeCallbacks (0.14s)\n"} +{"Time":"2026-10-18T01:46:05.473726839Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestLoginRejectsUnsafeCallbacks","Elapsed":0.14} +{"Time":"2026-10-18T01:46:05.473740365Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestOutputProtection"} +{"Time":"2026-10-18T01:46:05.4737461Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOutputProtection","Output":"=== RUN TestOutputProtection\n"} +{"Time":"2026-10-18T01:46:05.475385824Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOutputProtection","Output":"--- PASS: TestOutputProtection (0.00s)\n"} +{"Time":"2026-10-18T01:46:05.475423137Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestOutputProtection","Elapsed":0} +{"Time":"2026-10-18T01:46:05.475436032Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestDiscoveryAndRedirectBoundaries"} +{"Time":"2026-10-18T01:46:05.47544256Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestDiscoveryAndRedirectBoundaries","Output":"=== RUN TestDiscoveryAndRedirectBoundaries\n"} +{"Time":"2026-10-18T01:46:05.493732183Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestDiscoveryAndRedirectBoundaries","Output":"--- PASS: TestDiscoveryAndRedirectBoundaries (0.02s)\n"} +{"Time":"2026-10-18T01:46:05.493782752Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestDiscoveryAndRedirectBoundaries","Elapsed":0.02} +{"Time":"2026-10-18T01:46:05.493793014Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestNonceMismatchAndCancelledLogin"} +{"Time":"2026-10-18T01:46:05.493796657Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestNonceMismatchAndCancelledLogin","Output":"=== RUN TestNonceMismatchAndCancelledLogin\n"} +{"Time":"2026-10-18T01:46:05.638618014Z","Action":"output","Package":"keycape/internal/adapters/privacyidea","Output":"ok \tkeycape/internal/adapters/privacyidea\t1.067s\n"} +{"Time":"2026-10-18T01:46:05.638670233Z","Action":"pass","Package":"keycape/internal/adapters/privacyidea","Elapsed":1.067} +{"Time":"2026-10-18T01:46:05.72381733Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestNonceMismatchAndCancelledLogin","Output":"--- PASS: TestNonceMismatchAndCancelledLogin (0.23s)\n"} +{"Time":"2026-10-18T01:46:05.723883138Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestNonceMismatchAndCancelledLogin","Elapsed":0.23} +{"Time":"2026-10-18T01:46:05.72389239Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyRejectsForeignSignatureAndUnusableKeySet"} +{"Time":"2026-10-18T01:46:05.723896042Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyRejectsForeignSignatureAndUnusableKeySet","Output":"=== RUN TestVerifyRejectsForeignSignatureAndUnusableKeySet\n"} +{"Time":"2026-10-18T01:46:05.813200319Z","Action":"output","Package":"keycape/internal/adapters/tenantengine","Output":"ok \tkeycape/internal/adapters/tenantengine\t1.143s\n"} +{"Time":"2026-10-18T01:46:05.813274198Z","Action":"pass","Package":"keycape/internal/adapters/tenantengine","Elapsed":1.143} +{"Time":"2026-10-18T01:46:06.009627376Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyRejectsForeignSignatureAndUnusableKeySet","Output":"--- PASS: TestVerifyRejectsForeignSignatureAndUnusableKeySet (0.29s)\n"} +{"Time":"2026-10-18T01:46:06.009688252Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyRejectsForeignSignatureAndUnusableKeySet","Elapsed":0.29} +{"Time":"2026-10-18T01:46:06.00970662Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientPassesOnCorrectRegistration"} +{"Time":"2026-10-18T01:46:06.009733095Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientPassesOnCorrectRegistration","Output":"=== RUN TestVerifyClientPassesOnCorrectRegistration\n"} +{"Time":"2026-10-18T01:46:06.13367952Z","Action":"start","Package":"keycape/internal/domain"} +{"Time":"2026-10-18T01:46:06.148942007Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ClientOverrideIsPerClient"} +{"Time":"2026-10-18T01:46:06.14898693Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ClientOverrideIsPerClient","Output":"=== RUN TestDecideAssurance_ClientOverrideIsPerClient\n"} +{"Time":"2026-10-18T01:46:06.149246328Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ClientOverrideIsPerClient","Output":"--- PASS: TestDecideAssurance_ClientOverrideIsPerClient (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.149289864Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ClientOverrideIsPerClient","Elapsed":0} +{"Time":"2026-10-18T01:46:06.149310865Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ACRRaisesClientAAL1"} +{"Time":"2026-10-18T01:46:06.14931898Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ACRRaisesClientAAL1","Output":"=== RUN TestDecideAssurance_ACRRaisesClientAAL1\n"} +{"Time":"2026-10-18T01:46:06.149554466Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ACRRaisesClientAAL1","Output":"--- PASS: TestDecideAssurance_ACRRaisesClientAAL1 (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.14957966Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_ACRRaisesClientAAL1","Elapsed":0} +{"Time":"2026-10-18T01:46:06.149591617Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL1SessionCannotSatisfyAAL2"} +{"Time":"2026-10-18T01:46:06.149599155Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL1SessionCannotSatisfyAAL2","Output":"=== RUN TestDecideAssurance_AAL1SessionCannotSatisfyAAL2\n"} +{"Time":"2026-10-18T01:46:06.149830159Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL1SessionCannotSatisfyAAL2","Output":"--- PASS: TestDecideAssurance_AAL1SessionCannotSatisfyAAL2 (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.149853234Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL1SessionCannotSatisfyAAL2","Elapsed":0} +{"Time":"2026-10-18T01:46:06.149866463Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL2SessionSatisfiesHighAssurance"} +{"Time":"2026-10-18T01:46:06.149873902Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL2SessionSatisfiesHighAssurance","Output":"=== RUN TestDecideAssurance_AAL2SessionSatisfiesHighAssurance\n"} +{"Time":"2026-10-18T01:46:06.150078034Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL2SessionSatisfiesHighAssurance","Output":"--- PASS: TestDecideAssurance_AAL2SessionSatisfiesHighAssurance (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.150099775Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_AAL2SessionSatisfiesHighAssurance","Elapsed":0} +{"Time":"2026-10-18T01:46:06.150272933Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_MaxAgeInvalidatesSession"} +{"Time":"2026-10-18T01:46:06.150296279Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_MaxAgeInvalidatesSession","Output":"=== RUN TestDecideAssurance_MaxAgeInvalidatesSession\n"} +{"Time":"2026-10-18T01:46:06.150444072Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_MaxAgeInvalidatesSession","Output":"--- PASS: TestDecideAssurance_MaxAgeInvalidatesSession (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.150470699Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_MaxAgeInvalidatesSession","Elapsed":0} +{"Time":"2026-10-18T01:46:06.150515632Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_PromptLoginIgnoresSession"} +{"Time":"2026-10-18T01:46:06.150531187Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_PromptLoginIgnoresSession","Output":"=== RUN TestDecideAssurance_PromptLoginIgnoresSession\n"} +{"Time":"2026-10-18T01:46:06.15071284Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_PromptLoginIgnoresSession","Output":"--- PASS: TestDecideAssurance_PromptLoginIgnoresSession (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.150736456Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_PromptLoginIgnoresSession","Elapsed":0} +{"Time":"2026-10-18T01:46:06.150759531Z","Action":"run","Package":"keycape/internal/domain","Test":"TestDecideAssurance_SessionUserMismatchIgnored"} +{"Time":"2026-10-18T01:46:06.150769594Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_SessionUserMismatchIgnored","Output":"=== RUN TestDecideAssurance_SessionUserMismatchIgnored\n"} +{"Time":"2026-10-18T01:46:06.15094672Z","Action":"output","Package":"keycape/internal/domain","Test":"TestDecideAssurance_SessionUserMismatchIgnored","Output":"--- PASS: TestDecideAssurance_SessionUserMismatchIgnored (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.150971959Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestDecideAssurance_SessionUserMismatchIgnored","Elapsed":0} +{"Time":"2026-10-18T01:46:06.150995116Z","Action":"run","Package":"keycape/internal/domain","Test":"TestACRRequiresAAL2"} +{"Time":"2026-10-18T01:46:06.151004981Z","Action":"output","Package":"keycape/internal/domain","Test":"TestACRRequiresAAL2","Output":"=== RUN TestACRRequiresAAL2\n"} +{"Time":"2026-10-18T01:46:06.151235071Z","Action":"output","Package":"keycape/internal/domain","Test":"TestACRRequiresAAL2","Output":"--- PASS: TestACRRequiresAAL2 (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.151273565Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestACRRequiresAAL2","Elapsed":0} +{"Time":"2026-10-18T01:46:06.151304918Z","Action":"run","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeClient"} +{"Time":"2026-10-18T01:46:06.151312249Z","Action":"output","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeClient","Output":"=== RUN TestCanonicalModelMatchesRuntimeClient\n"} +{"Time":"2026-10-18T01:46:06.162221675Z","Action":"output","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeClient","Output":"--- PASS: TestCanonicalModelMatchesRuntimeClient (0.01s)\n"} +{"Time":"2026-10-18T01:46:06.162285409Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeClient","Elapsed":0.01} +{"Time":"2026-10-18T01:46:06.162302713Z","Action":"run","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeDirectoryEntities"} +{"Time":"2026-10-18T01:46:06.162308863Z","Action":"output","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeDirectoryEntities","Output":"=== RUN TestCanonicalModelMatchesRuntimeDirectoryEntities\n"} +{"Time":"2026-10-18T01:46:06.172983438Z","Action":"output","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeDirectoryEntities","Output":"--- PASS: TestCanonicalModelMatchesRuntimeDirectoryEntities (0.01s)\n"} +{"Time":"2026-10-18T01:46:06.173040175Z","Action":"pass","Package":"keycape/internal/domain","Test":"TestCanonicalModelMatchesRuntimeDirectoryEntities","Elapsed":0.01} +{"Time":"2026-10-18T01:46:06.173053791Z","Action":"output","Package":"keycape/internal/domain","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:06.303361337Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientPassesOnCorrectRegistration","Output":"--- PASS: TestVerifyClientPassesOnCorrectRegistration (0.29s)\n"} +{"Time":"2026-10-18T01:46:06.30344893Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientPassesOnCorrectRegistration","Elapsed":0.29} +{"Time":"2026-10-18T01:46:06.303476722Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientNeverPrintsSecretsOrTokens"} +{"Time":"2026-10-18T01:46:06.303482736Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientNeverPrintsSecretsOrTokens","Output":"=== RUN TestVerifyClientNeverPrintsSecretsOrTokens\n"} +{"Time":"2026-10-18T01:46:06.308699694Z","Action":"output","Package":"keycape/internal/config","Output":"ok \tkeycape/internal/config\t1.063s\n"} +{"Time":"2026-10-18T01:46:06.308757287Z","Action":"pass","Package":"keycape/internal/config","Elapsed":1.063} +{"Time":"2026-10-18T01:46:06.379599068Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientNeverPrintsSecretsOrTokens","Output":"--- PASS: TestVerifyClientNeverPrintsSecretsOrTokens (0.08s)\n"} +{"Time":"2026-10-18T01:46:06.379653415Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientNeverPrintsSecretsOrTokens","Elapsed":0.08} +{"Time":"2026-10-18T01:46:06.379679547Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientFailsWhenExcessScopeIsGranted"} +{"Time":"2026-10-18T01:46:06.379690097Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientFailsWhenExcessScopeIsGranted","Output":"=== RUN TestVerifyClientFailsWhenExcessScopeIsGranted\n"} +{"Time":"2026-10-18T01:46:06.473538132Z","Action":"start","Package":"keycape/internal/errors"} +{"Time":"2026-10-18T01:46:06.491275433Z","Action":"run","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants"} +{"Time":"2026-10-18T01:46:06.491317417Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants","Output":"=== RUN TestErrorTypeConstants\n"} +{"Time":"2026-10-18T01:46:06.491476797Z","Action":"run","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/FeatureNotSupported"} +{"Time":"2026-10-18T01:46:06.491488402Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/FeatureNotSupported","Output":"=== RUN TestErrorTypeConstants/FeatureNotSupported\n"} +{"Time":"2026-10-18T01:46:06.491729424Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/FeatureNotSupported","Output":"--- PASS: TestErrorTypeConstants/FeatureNotSupported (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.491862636Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/FeatureNotSupported","Elapsed":0} +{"Time":"2026-10-18T01:46:06.491878542Z","Action":"run","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/KeycloakModeOnly"} +{"Time":"2026-10-18T01:46:06.491883736Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/KeycloakModeOnly","Output":"=== RUN TestErrorTypeConstants/KeycloakModeOnly\n"} +{"Time":"2026-10-18T01:46:06.492505192Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/KeycloakModeOnly","Output":"--- PASS: TestErrorTypeConstants/KeycloakModeOnly (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.492552001Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/KeycloakModeOnly","Elapsed":0} +{"Time":"2026-10-18T01:46:06.492647497Z","Action":"run","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/RejectedForSafety"} +{"Time":"2026-10-18T01:46:06.492663466Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/RejectedForSafety","Output":"=== RUN TestErrorTypeConstants/RejectedForSafety\n"} +{"Time":"2026-10-18T01:46:06.492881504Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/RejectedForSafety","Output":"--- PASS: TestErrorTypeConstants/RejectedForSafety (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.492900115Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/RejectedForSafety","Elapsed":0} +{"Time":"2026-10-18T01:46:06.493048178Z","Action":"run","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:06.493082705Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/InvalidProfileUsage","Output":"=== RUN TestErrorTypeConstants/InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:06.493260712Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/InvalidProfileUsage","Output":"--- PASS: TestErrorTypeConstants/InvalidProfileUsage (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.493280495Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants/InvalidProfileUsage","Elapsed":0} +{"Time":"2026-10-18T01:46:06.493298178Z","Action":"output","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants","Output":"--- PASS: TestErrorTypeConstants (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.493463284Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestErrorTypeConstants","Elapsed":0} +{"Time":"2026-10-18T01:46:06.493503528Z","Action":"run","Package":"keycape/internal/errors","Test":"TestConstructorHelpers"} +{"Time":"2026-10-18T01:46:06.493510345Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers","Output":"=== RUN TestConstructorHelpers\n"} +{"Time":"2026-10-18T01:46:06.493713257Z","Action":"run","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/FeatureNotSupported"} +{"Time":"2026-10-18T01:46:06.493737955Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/FeatureNotSupported","Output":"=== RUN TestConstructorHelpers/FeatureNotSupported\n"} +{"Time":"2026-10-18T01:46:06.493923107Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/FeatureNotSupported","Output":"--- PASS: TestConstructorHelpers/FeatureNotSupported (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.493942205Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/FeatureNotSupported","Elapsed":0} +{"Time":"2026-10-18T01:46:06.493952124Z","Action":"run","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/KeycloakModeOnly"} +{"Time":"2026-10-18T01:46:06.493957327Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/KeycloakModeOnly","Output":"=== RUN TestConstructorHelpers/KeycloakModeOnly\n"} +{"Time":"2026-10-18T01:46:06.494192434Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/KeycloakModeOnly","Output":"--- PASS: TestConstructorHelpers/KeycloakModeOnly (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.494221091Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/KeycloakModeOnly","Elapsed":0} +{"Time":"2026-10-18T01:46:06.494231875Z","Action":"run","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/RejectedForSafety"} +{"Time":"2026-10-18T01:46:06.494236808Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/RejectedForSafety","Output":"=== RUN TestConstructorHelpers/RejectedForSafety\n"} +{"Time":"2026-10-18T01:46:06.494517574Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/RejectedForSafety","Output":"--- PASS: TestConstructorHelpers/RejectedForSafety (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.49455597Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/RejectedForSafety","Elapsed":0} +{"Time":"2026-10-18T01:46:06.49457771Z","Action":"run","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:06.494584563Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/InvalidProfileUsage","Output":"=== RUN TestConstructorHelpers/InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:06.494962239Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/InvalidProfileUsage","Output":"--- PASS: TestConstructorHelpers/InvalidProfileUsage (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.495016722Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestConstructorHelpers/InvalidProfileUsage","Elapsed":0} +{"Time":"2026-10-18T01:46:06.495154626Z","Action":"output","Package":"keycape/internal/errors","Test":"TestConstructorHelpers","Output":"--- PASS: TestConstructorHelpers (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.495182471Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestConstructorHelpers","Elapsed":0} +{"Time":"2026-10-18T01:46:06.49520083Z","Action":"run","Package":"keycape/internal/errors","Test":"TestProfileErrorJSON"} +{"Time":"2026-10-18T01:46:06.495210253Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorJSON","Output":"=== RUN TestProfileErrorJSON\n"} +{"Time":"2026-10-18T01:46:06.495903723Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorJSON","Output":"--- PASS: TestProfileErrorJSON (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.495942794Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestProfileErrorJSON","Elapsed":0} +{"Time":"2026-10-18T01:46:06.495953534Z","Action":"run","Package":"keycape/internal/errors","Test":"TestProfileErrorOmitsFeatureWhenEmpty"} +{"Time":"2026-10-18T01:46:06.495957943Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorOmitsFeatureWhenEmpty","Output":"=== RUN TestProfileErrorOmitsFeatureWhenEmpty\n"} +{"Time":"2026-10-18T01:46:06.496258948Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorOmitsFeatureWhenEmpty","Output":"--- PASS: TestProfileErrorOmitsFeatureWhenEmpty (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.496305721Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestProfileErrorOmitsFeatureWhenEmpty","Elapsed":0} +{"Time":"2026-10-18T01:46:06.496318327Z","Action":"run","Package":"keycape/internal/errors","Test":"TestProfileErrorWrite"} +{"Time":"2026-10-18T01:46:06.496322421Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorWrite","Output":"=== RUN TestProfileErrorWrite\n"} +{"Time":"2026-10-18T01:46:06.496636356Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorWrite","Output":"--- PASS: TestProfileErrorWrite (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.496661586Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestProfileErrorWrite","Elapsed":0} +{"Time":"2026-10-18T01:46:06.496699522Z","Action":"run","Package":"keycape/internal/errors","Test":"TestProfileErrorGoError"} +{"Time":"2026-10-18T01:46:06.49670532Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorGoError","Output":"=== RUN TestProfileErrorGoError\n"} +{"Time":"2026-10-18T01:46:06.496912392Z","Action":"output","Package":"keycape/internal/errors","Test":"TestProfileErrorGoError","Output":"--- PASS: TestProfileErrorGoError (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.496925566Z","Action":"pass","Package":"keycape/internal/errors","Test":"TestProfileErrorGoError","Elapsed":0} +{"Time":"2026-10-18T01:46:06.49694407Z","Action":"output","Package":"keycape/internal/errors","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:06.731155699Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientFailsWhenExcessScopeIsGranted","Output":"--- PASS: TestVerifyClientFailsWhenExcessScopeIsGranted (0.35s)\n"} +{"Time":"2026-10-18T01:46:06.731212065Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientFailsWhenExcessScopeIsGranted","Elapsed":0.35} +{"Time":"2026-10-18T01:46:06.731224058Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientRejectsUnrotatedAndAcceptedPredecessor"} +{"Time":"2026-10-18T01:46:06.731229243Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientRejectsUnrotatedAndAcceptedPredecessor","Output":"=== RUN TestVerifyClientRejectsUnrotatedAndAcceptedPredecessor\n"} +{"Time":"2026-10-18T01:46:06.79137767Z","Action":"start","Package":"keycape/internal/jose"} +{"Time":"2026-10-18T01:46:06.825582983Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientRejectsUnrotatedAndAcceptedPredecessor","Output":"--- PASS: TestVerifyClientRejectsUnrotatedAndAcceptedPredecessor (0.09s)\n"} +{"Time":"2026-10-18T01:46:06.825640684Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientRejectsUnrotatedAndAcceptedPredecessor","Elapsed":0.09} +{"Time":"2026-10-18T01:46:06.825657619Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientRequiresCompleteArguments"} +{"Time":"2026-10-18T01:46:06.825662957Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientRequiresCompleteArguments","Output":"=== RUN TestVerifyClientRequiresCompleteArguments\n"} +{"Time":"2026-10-18T01:46:06.825953673Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientRequiresCompleteArguments","Output":"--- PASS: TestVerifyClientRequiresCompleteArguments (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.825968164Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientRequiresCompleteArguments","Elapsed":0} +{"Time":"2026-10-18T01:46:06.826059057Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestNoExcessScopeCatchesUnrequestedGrants"} +{"Time":"2026-10-18T01:46:06.826069887Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestNoExcessScopeCatchesUnrequestedGrants","Output":"=== RUN TestNoExcessScopeCatchesUnrequestedGrants\n"} +{"Time":"2026-10-18T01:46:06.826269592Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestNoExcessScopeCatchesUnrequestedGrants","Output":"--- PASS: TestNoExcessScopeCatchesUnrequestedGrants (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.826283226Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestNoExcessScopeCatchesUnrequestedGrants","Elapsed":0} +{"Time":"2026-10-18T01:46:06.826376302Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestHasAllRolesNamesWhatIsMissing"} +{"Time":"2026-10-18T01:46:06.826386672Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestHasAllRolesNamesWhatIsMissing","Output":"=== RUN TestHasAllRolesNamesWhatIsMissing\n"} +{"Time":"2026-10-18T01:46:06.826575142Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestHasAllRolesNamesWhatIsMissing","Output":"--- PASS: TestHasAllRolesNamesWhatIsMissing (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.826588965Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestHasAllRolesNamesWhatIsMissing","Elapsed":0} +{"Time":"2026-10-18T01:46:06.826675846Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals"} +{"Time":"2026-10-18T01:46:06.826685549Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals\n"} +{"Time":"2026-10-18T01:46:06.826835127Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/transport"} +{"Time":"2026-10-18T01:46:06.826846489Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/transport","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/transport\n"} +{"Time":"2026-10-18T01:46:06.886426896Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyAcceptsGenuineTokenAndReturnsClaims"} +{"Time":"2026-10-18T01:46:06.886489124Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyAcceptsGenuineTokenAndReturnsClaims","Output":"=== RUN TestVerifyAcceptsGenuineTokenAndReturnsClaims\n"} +{"Time":"2026-10-18T01:46:06.889644918Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyAcceptsGenuineTokenAndReturnsClaims","Output":"--- PASS: TestVerifyAcceptsGenuineTokenAndReturnsClaims (0.00s)\n"} +{"Time":"2026-10-18T01:46:06.889706569Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyAcceptsGenuineTokenAndReturnsClaims","Elapsed":0} +{"Time":"2026-10-18T01:46:06.889738959Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections"} +{"Time":"2026-10-18T01:46:06.889747841Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections","Output":"=== RUN TestVerifyRejections\n"} +{"Time":"2026-10-18T01:46:06.915617399Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/transport","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/transport (0.09s)\n"} +{"Time":"2026-10-18T01:46:06.915674965Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/transport","Elapsed":0.09} +{"Time":"2026-10-18T01:46:06.915695767Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/server"} +{"Time":"2026-10-18T01:46:06.915702494Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/server","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/server\n"} +{"Time":"2026-10-18T01:46:07.050924339Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/empty"} +{"Time":"2026-10-18T01:46:07.051006098Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/empty","Output":"=== RUN TestVerifyRejections/empty\n"} +{"Time":"2026-10-18T01:46:07.051048542Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/empty","Output":"--- PASS: TestVerifyRejections/empty (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.051064936Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/empty","Elapsed":0} +{"Time":"2026-10-18T01:46:07.051079084Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/wrong_signing_key"} +{"Time":"2026-10-18T01:46:07.051114981Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/wrong_signing_key","Output":"=== RUN TestVerifyRejections/wrong_signing_key\n"} +{"Time":"2026-10-18T01:46:07.051306066Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/wrong_signing_key","Output":"--- PASS: TestVerifyRejections/wrong_signing_key (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.051328429Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/wrong_signing_key","Elapsed":0} +{"Time":"2026-10-18T01:46:07.051349691Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/unknown_key_id"} +{"Time":"2026-10-18T01:46:07.051357572Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/unknown_key_id","Output":"=== RUN TestVerifyRejections/unknown_key_id\n"} +{"Time":"2026-10-18T01:46:07.05152994Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/unknown_key_id","Output":"--- PASS: TestVerifyRejections/unknown_key_id (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.051566874Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/unknown_key_id","Elapsed":0} +{"Time":"2026-10-18T01:46:07.051580806Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_HS256"} +{"Time":"2026-10-18T01:46:07.051591627Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_HS256","Output":"=== RUN TestVerifyRejections/alg_HS256\n"} +{"Time":"2026-10-18T01:46:07.051611789Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_HS256","Output":"--- PASS: TestVerifyRejections/alg_HS256 (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.051635162Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_HS256","Elapsed":0} +{"Time":"2026-10-18T01:46:07.0516627Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/tampered_payload"} +{"Time":"2026-10-18T01:46:07.051681862Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/tampered_payload","Output":"=== RUN TestVerifyRejections/tampered_payload\n"} +{"Time":"2026-10-18T01:46:07.051949386Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/tampered_payload","Output":"--- PASS: TestVerifyRejections/tampered_payload (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.051972163Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/tampered_payload","Elapsed":0} +{"Time":"2026-10-18T01:46:07.051981938Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/payload_not_json"} +{"Time":"2026-10-18T01:46:07.051989819Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/payload_not_json","Output":"=== RUN TestVerifyRejections/payload_not_json\n"} +{"Time":"2026-10-18T01:46:07.0522325Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/payload_not_json","Output":"--- PASS: TestVerifyRejections/payload_not_json (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.052282375Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/payload_not_json","Elapsed":0} +{"Time":"2026-10-18T01:46:07.052305179Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/missing_key_id"} +{"Time":"2026-10-18T01:46:07.052314675Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/missing_key_id","Output":"=== RUN TestVerifyRejections/missing_key_id\n"} +{"Time":"2026-10-18T01:46:07.052343187Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/missing_key_id","Output":"--- PASS: TestVerifyRejections/missing_key_id (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.052357452Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/missing_key_id","Elapsed":0} +{"Time":"2026-10-18T01:46:07.052370527Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_none"} +{"Time":"2026-10-18T01:46:07.052378345Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_none","Output":"=== RUN TestVerifyRejections/alg_none\n"} +{"Time":"2026-10-18T01:46:07.052513088Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_none","Output":"--- PASS: TestVerifyRejections/alg_none (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.052537425Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/alg_none","Elapsed":0} +{"Time":"2026-10-18T01:46:07.052544305Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/critical_extension"} +{"Time":"2026-10-18T01:46:07.052585216Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/critical_extension","Output":"=== RUN TestVerifyRejections/critical_extension\n"} +{"Time":"2026-10-18T01:46:07.052802481Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/critical_extension","Output":"--- PASS: TestVerifyRejections/critical_extension (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.052827612Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/critical_extension","Elapsed":0} +{"Time":"2026-10-18T01:46:07.052834979Z","Action":"run","Package":"keycape/internal/jose","Test":"TestVerifyRejections/two_segments"} +{"Time":"2026-10-18T01:46:07.052840479Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/two_segments","Output":"=== RUN TestVerifyRejections/two_segments\n"} +{"Time":"2026-10-18T01:46:07.052893014Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections/two_segments","Output":"--- PASS: TestVerifyRejections/two_segments (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.052906206Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections/two_segments","Elapsed":0} +{"Time":"2026-10-18T01:46:07.052925936Z","Action":"output","Package":"keycape/internal/jose","Test":"TestVerifyRejections","Output":"--- PASS: TestVerifyRejections (0.16s)\n"} +{"Time":"2026-10-18T01:46:07.053012357Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestVerifyRejections","Elapsed":0.16} +{"Time":"2026-10-18T01:46:07.053042953Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets"} +{"Time":"2026-10-18T01:46:07.053048805Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets\n"} +{"Time":"2026-10-18T01:46:07.074791198Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/server","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/server (0.16s)\n"} +{"Time":"2026-10-18T01:46:07.074841117Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/server","Elapsed":0.16} +{"Time":"2026-10-18T01:46:07.074855257Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/invalid_token"} +{"Time":"2026-10-18T01:46:07.074860144Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/invalid_token","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/invalid_token\n"} +{"Time":"2026-10-18T01:46:07.082868105Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/undersized_modulus"} +{"Time":"2026-10-18T01:46:07.082923823Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/undersized_modulus","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/undersized_modulus\n"} +{"Time":"2026-10-18T01:46:07.08311754Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/undersized_modulus","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/undersized_modulus (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.083139416Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/undersized_modulus","Elapsed":0} +{"Time":"2026-10-18T01:46:07.083147973Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/empty_set"} +{"Time":"2026-10-18T01:46:07.083153816Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/empty_set","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/empty_set\n"} +{"Time":"2026-10-18T01:46:07.083304143Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/empty_set","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/empty_set (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.083320004Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/empty_set","Elapsed":0} +{"Time":"2026-10-18T01:46:07.083329211Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/no_rsa_signing_key"} +{"Time":"2026-10-18T01:46:07.083334892Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/no_rsa_signing_key","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/no_rsa_signing_key\n"} +{"Time":"2026-10-18T01:46:07.083510512Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/no_rsa_signing_key","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/no_rsa_signing_key (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.083530575Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/no_rsa_signing_key","Elapsed":0} +{"Time":"2026-10-18T01:46:07.08354025Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/missing_key_id"} +{"Time":"2026-10-18T01:46:07.083545823Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/missing_key_id","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/missing_key_id\n"} +{"Time":"2026-10-18T01:46:07.083794257Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/missing_key_id","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/missing_key_id (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.083817621Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/missing_key_id","Elapsed":0} +{"Time":"2026-10-18T01:46:07.083828676Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/bad_base64"} +{"Time":"2026-10-18T01:46:07.083834258Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/bad_base64","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/bad_base64\n"} +{"Time":"2026-10-18T01:46:07.083969225Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/bad_base64","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/bad_base64 (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.083989432Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/bad_base64","Elapsed":0} +{"Time":"2026-10-18T01:46:07.084093891Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/even_exponent"} +{"Time":"2026-10-18T01:46:07.084126759Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/even_exponent","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/even_exponent\n"} +{"Time":"2026-10-18T01:46:07.084372476Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/even_exponent","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/even_exponent (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.084390997Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/even_exponent","Elapsed":0} +{"Time":"2026-10-18T01:46:07.08449772Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/not_json"} +{"Time":"2026-10-18T01:46:07.084516657Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/not_json","Output":"=== RUN TestParseJWKSRejectsUnusableKeySets/not_json\n"} +{"Time":"2026-10-18T01:46:07.084605549Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/not_json","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets/not_json (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.084628831Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets/not_json","Elapsed":0} +{"Time":"2026-10-18T01:46:07.084659842Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets","Output":"--- PASS: TestParseJWKSRejectsUnusableKeySets (0.03s)\n"} +{"Time":"2026-10-18T01:46:07.084757408Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsUnusableKeySets","Elapsed":0.03} +{"Time":"2026-10-18T01:46:07.084780222Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsDuplicateKeyIDs"} +{"Time":"2026-10-18T01:46:07.084791331Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsDuplicateKeyIDs","Output":"=== RUN TestParseJWKSRejectsDuplicateKeyIDs\n"} +{"Time":"2026-10-18T01:46:07.085406785Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsDuplicateKeyIDs","Output":"--- PASS: TestParseJWKSRejectsDuplicateKeyIDs (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.085421078Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSRejectsDuplicateKeyIDs","Elapsed":0} +{"Time":"2026-10-18T01:46:07.085432421Z","Action":"run","Package":"keycape/internal/jose","Test":"TestParseJWKSIgnoresIrrelevantKeys"} +{"Time":"2026-10-18T01:46:07.085436506Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSIgnoresIrrelevantKeys","Output":"=== RUN TestParseJWKSIgnoresIrrelevantKeys\n"} +{"Time":"2026-10-18T01:46:07.085925295Z","Action":"output","Package":"keycape/internal/jose","Test":"TestParseJWKSIgnoresIrrelevantKeys","Output":"--- PASS: TestParseJWKSIgnoresIrrelevantKeys (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.08595296Z","Action":"pass","Package":"keycape/internal/jose","Test":"TestParseJWKSIgnoresIrrelevantKeys","Elapsed":0} +{"Time":"2026-10-18T01:46:07.086040112Z","Action":"output","Package":"keycape/internal/jose","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:07.171602477Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/invalid_token","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/invalid_token (0.10s)\n"} +{"Time":"2026-10-18T01:46:07.171652262Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/invalid_token","Elapsed":0.1} +{"Time":"2026-10-18T01:46:07.171660395Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/unrelated_refusal"} +{"Time":"2026-10-18T01:46:07.17166466Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/unrelated_refusal","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/unrelated_refusal\n"} +{"Time":"2026-10-18T01:46:07.177170595Z","Action":"output","Package":"keycape/internal/domain","Output":"ok \tkeycape/internal/domain\t1.043s\n"} +{"Time":"2026-10-18T01:46:07.177238098Z","Action":"pass","Package":"keycape/internal/domain","Elapsed":1.044} +{"Time":"2026-10-18T01:46:07.464418184Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/unrelated_refusal","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/unrelated_refusal (0.29s)\n"} +{"Time":"2026-10-18T01:46:07.464481278Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/unrelated_refusal","Elapsed":0.29} +{"Time":"2026-10-18T01:46:07.464493424Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/malformed_refusal"} +{"Time":"2026-10-18T01:46:07.464500855Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/malformed_refusal","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/malformed_refusal\n"} +{"Time":"2026-10-18T01:46:07.500494449Z","Action":"output","Package":"keycape/internal/errors","Output":"ok \tkeycape/internal/errors\t1.027s\n"} +{"Time":"2026-10-18T01:46:07.500562655Z","Action":"pass","Package":"keycape/internal/errors","Elapsed":1.027} +{"Time":"2026-10-18T01:46:07.677875097Z","Action":"start","Package":"keycape/internal/migration/lldapexport"} +{"Time":"2026-10-18T01:46:07.694444788Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_UsersAndGroups"} +{"Time":"2026-10-18T01:46:07.694557161Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_UsersAndGroups","Output":"=== RUN TestExporter_Export_UsersAndGroups\n"} +{"Time":"2026-10-18T01:46:07.696356596Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_UsersAndGroups","Output":"--- PASS: TestExporter_Export_UsersAndGroups (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.696402205Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_UsersAndGroups","Elapsed":0} +{"Time":"2026-10-18T01:46:07.696417048Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_WritesYAMLFile"} +{"Time":"2026-10-18T01:46:07.696425848Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_WritesYAMLFile","Output":"=== RUN TestExporter_Export_WritesYAMLFile\n"} +{"Time":"2026-10-18T01:46:07.697758273Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_WritesYAMLFile","Output":"--- PASS: TestExporter_Export_WritesYAMLFile (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.697809428Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_WritesYAMLFile","Elapsed":0} +{"Time":"2026-10-18T01:46:07.69781969Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_EmitsMigrationEvent"} +{"Time":"2026-10-18T01:46:07.697824496Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_EmitsMigrationEvent","Output":"=== RUN TestExporter_Export_EmitsMigrationEvent\n"} +{"Time":"2026-10-18T01:46:07.698856871Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_EmitsMigrationEvent","Output":"--- PASS: TestExporter_Export_EmitsMigrationEvent (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.69890028Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_EmitsMigrationEvent","Elapsed":0} +{"Time":"2026-10-18T01:46:07.698915384Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncompatibilityReport_BadUser"} +{"Time":"2026-10-18T01:46:07.698919695Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncompatibilityReport_BadUser","Output":"=== RUN TestExporter_Export_IncompatibilityReport_BadUser\n"} +{"Time":"2026-10-18T01:46:07.7000285Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncompatibilityReport_BadUser","Output":"--- PASS: TestExporter_Export_IncompatibilityReport_BadUser (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.700061513Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncompatibilityReport_BadUser","Elapsed":0} +{"Time":"2026-10-18T01:46:07.700078934Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_BuildsMemberships"} +{"Time":"2026-10-18T01:46:07.700083686Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_BuildsMemberships","Output":"=== RUN TestExporter_Export_BuildsMemberships\n"} +{"Time":"2026-10-18T01:46:07.70165266Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_BuildsMemberships","Output":"--- PASS: TestExporter_Export_BuildsMemberships (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.701705925Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_BuildsMemberships","Elapsed":0} +{"Time":"2026-10-18T01:46:07.701713878Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_ProfileVersion"} +{"Time":"2026-10-18T01:46:07.701719298Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_ProfileVersion","Output":"=== RUN TestExporter_Export_ProfileVersion\n"} +{"Time":"2026-10-18T01:46:07.702995072Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_ProfileVersion","Output":"--- PASS: TestExporter_Export_ProfileVersion (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.70305648Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_ProfileVersion","Elapsed":0} +{"Time":"2026-10-18T01:46:07.703077571Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncludesGroupWithNoMembers"} +{"Time":"2026-10-18T01:46:07.703091052Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncludesGroupWithNoMembers","Output":"=== RUN TestExporter_Export_IncludesGroupWithNoMembers\n"} +{"Time":"2026-10-18T01:46:07.704839406Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncludesGroupWithNoMembers","Output":"--- PASS: TestExporter_Export_IncludesGroupWithNoMembers (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.704904799Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IncludesGroupWithNoMembers","Elapsed":0} +{"Time":"2026-10-18T01:46:07.70491516Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_GroupEnumerationFailureIsFatal"} +{"Time":"2026-10-18T01:46:07.70491966Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_GroupEnumerationFailureIsFatal","Output":"=== RUN TestExporter_Export_GroupEnumerationFailureIsFatal\n"} +{"Time":"2026-10-18T01:46:07.705392439Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_GroupEnumerationFailureIsFatal","Output":"--- PASS: TestExporter_Export_GroupEnumerationFailureIsFatal (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.705433305Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_GroupEnumerationFailureIsFatal","Elapsed":0} +{"Time":"2026-10-18T01:46:07.705443918Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_FallbackReportsIncompleteness"} +{"Time":"2026-10-18T01:46:07.705450185Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_FallbackReportsIncompleteness","Output":"=== RUN TestExporter_Export_FallbackReportsIncompleteness\n"} +{"Time":"2026-10-18T01:46:07.707251832Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_FallbackReportsIncompleteness","Output":"--- PASS: TestExporter_Export_FallbackReportsIncompleteness (0.00s)\n"} +{"Time":"2026-10-18T01:46:07.707295944Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_FallbackReportsIncompleteness","Elapsed":0} +{"Time":"2026-10-18T01:46:07.707309452Z","Action":"run","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IsDeterministic"} +{"Time":"2026-10-18T01:46:07.7073236Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IsDeterministic","Output":"=== RUN TestExporter_Export_IsDeterministic\n"} +{"Time":"2026-10-18T01:46:07.713454901Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IsDeterministic","Output":"--- PASS: TestExporter_Export_IsDeterministic (0.01s)\n"} +{"Time":"2026-10-18T01:46:07.713505659Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Test":"TestExporter_Export_IsDeterministic","Elapsed":0.01} +{"Time":"2026-10-18T01:46:07.713518319Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:07.752881753Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/malformed_refusal","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/malformed_refusal (0.29s)\n"} +{"Time":"2026-10-18T01:46:07.752930753Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/scope/malformed_refusal","Elapsed":0.29} +{"Time":"2026-10-18T01:46:07.753318539Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/transport"} +{"Time":"2026-10-18T01:46:07.753342724Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/transport","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/transport\n"} +{"Time":"2026-10-18T01:46:08.068333681Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/transport","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/transport (0.32s)\n"} +{"Time":"2026-10-18T01:46:08.068401229Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/transport","Elapsed":0.32} +{"Time":"2026-10-18T01:46:08.068430174Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/server"} +{"Time":"2026-10-18T01:46:08.068436477Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/server","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/server\n"} +{"Time":"2026-10-18T01:46:08.090276051Z","Action":"output","Package":"keycape/internal/jose","Output":"ok \tkeycape/internal/jose\t1.299s\n"} +{"Time":"2026-10-18T01:46:08.090316574Z","Action":"pass","Package":"keycape/internal/jose","Elapsed":1.299} +{"Time":"2026-10-18T01:46:08.090785747Z","Action":"start","Package":"keycape/internal/migration/tokeycloak"} +{"Time":"2026-10-18T01:46:08.108285036Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_UsernameAndEmail"} +{"Time":"2026-10-18T01:46:08.1083418Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_UsernameAndEmail","Output":"=== RUN TestTransformer_UserMapping_UsernameAndEmail\n"} +{"Time":"2026-10-18T01:46:08.108536639Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_UsernameAndEmail","Output":"--- PASS: TestTransformer_UserMapping_UsernameAndEmail (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.108566008Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_UsernameAndEmail","Elapsed":0} +{"Time":"2026-10-18T01:46:08.108578461Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSplit"} +{"Time":"2026-10-18T01:46:08.108582762Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSplit","Output":"=== RUN TestTransformer_UserMapping_DisplayNameSplit\n"} +{"Time":"2026-10-18T01:46:08.108879636Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSplit","Output":"--- PASS: TestTransformer_UserMapping_DisplayNameSplit (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.108908528Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSplit","Elapsed":0} +{"Time":"2026-10-18T01:46:08.108926824Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSingleWord"} +{"Time":"2026-10-18T01:46:08.1089353Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSingleWord","Output":"=== RUN TestTransformer_UserMapping_DisplayNameSingleWord\n"} +{"Time":"2026-10-18T01:46:08.109126113Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSingleWord","Output":"--- PASS: TestTransformer_UserMapping_DisplayNameSingleWord (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.10927635Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_DisplayNameSingleWord","Elapsed":0} +{"Time":"2026-10-18T01:46:08.109345945Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_GroupPaths"} +{"Time":"2026-10-18T01:46:08.109355494Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_GroupPaths","Output":"=== RUN TestTransformer_UserMapping_GroupPaths\n"} +{"Time":"2026-10-18T01:46:08.109582332Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_GroupPaths","Output":"--- PASS: TestTransformer_UserMapping_GroupPaths (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.109803019Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_UserMapping_GroupPaths","Elapsed":0} +{"Time":"2026-10-18T01:46:08.109841893Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_GroupMapping_NameAndPath"} +{"Time":"2026-10-18T01:46:08.109851731Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_GroupMapping_NameAndPath","Output":"=== RUN TestTransformer_GroupMapping_NameAndPath\n"} +{"Time":"2026-10-18T01:46:08.109997462Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_GroupMapping_NameAndPath","Output":"--- PASS: TestTransformer_GroupMapping_NameAndPath (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.110041097Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_GroupMapping_NameAndPath","Elapsed":0} +{"Time":"2026-10-18T01:46:08.110178697Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_PublicClient"} +{"Time":"2026-10-18T01:46:08.110198355Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_PublicClient","Output":"=== RUN TestTransformer_ClientMapping_PublicClient\n"} +{"Time":"2026-10-18T01:46:08.110400522Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_PublicClient","Output":"--- PASS: TestTransformer_ClientMapping_PublicClient (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.110415914Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_PublicClient","Elapsed":0} +{"Time":"2026-10-18T01:46:08.110500947Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ConfidentialClient"} +{"Time":"2026-10-18T01:46:08.110511569Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ConfidentialClient","Output":"=== RUN TestTransformer_ClientMapping_ConfidentialClient\n"} +{"Time":"2026-10-18T01:46:08.110720138Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ConfidentialClient","Output":"--- PASS: TestTransformer_ClientMapping_ConfidentialClient (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.110736865Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ConfidentialClient","Elapsed":0} +{"Time":"2026-10-18T01:46:08.110760851Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_AllowedScopesBecomesDefaultClientScopes"} +{"Time":"2026-10-18T01:46:08.110767037Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_AllowedScopesBecomesDefaultClientScopes","Output":"=== RUN TestTransformer_ClientMapping_AllowedScopesBecomesDefaultClientScopes\n"} +{"Time":"2026-10-18T01:46:08.11099621Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_AllowedScopesBecomesDefaultClientScopes","Output":"--- PASS: TestTransformer_ClientMapping_AllowedScopesBecomesDefaultClientScopes (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.111030665Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_AllowedScopesBecomesDefaultClientScopes","Elapsed":0} +{"Time":"2026-10-18T01:46:08.111052613Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ImplicitFlowAlwaysFalse"} +{"Time":"2026-10-18T01:46:08.111061549Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ImplicitFlowAlwaysFalse","Output":"=== RUN TestTransformer_ClientMapping_ImplicitFlowAlwaysFalse\n"} +{"Time":"2026-10-18T01:46:08.111296917Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ImplicitFlowAlwaysFalse","Output":"--- PASS: TestTransformer_ClientMapping_ImplicitFlowAlwaysFalse (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.1113346Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ClientMapping_ImplicitFlowAlwaysFalse","Elapsed":0} +{"Time":"2026-10-18T01:46:08.111349434Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_IdentityProviders_AlwaysEmpty"} +{"Time":"2026-10-18T01:46:08.111355647Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_IdentityProviders_AlwaysEmpty","Output":"=== RUN TestTransformer_IdentityProviders_AlwaysEmpty\n"} +{"Time":"2026-10-18T01:46:08.111586308Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_IdentityProviders_AlwaysEmpty","Output":"--- PASS: TestTransformer_IdentityProviders_AlwaysEmpty (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.11160363Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_IdentityProviders_AlwaysEmpty","Elapsed":0} +{"Time":"2026-10-18T01:46:08.111611331Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ProfileVersion_RS256"} +{"Time":"2026-10-18T01:46:08.11161611Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ProfileVersion_RS256","Output":"=== RUN TestTransformer_ProfileVersion_RS256\n"} +{"Time":"2026-10-18T01:46:08.111909277Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ProfileVersion_RS256","Output":"--- PASS: TestTransformer_ProfileVersion_RS256 (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.112001082Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ProfileVersion_RS256","Elapsed":0} +{"Time":"2026-10-18T01:46:08.112014419Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_RealmMetadata"} +{"Time":"2026-10-18T01:46:08.112020154Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_RealmMetadata","Output":"=== RUN TestTransformer_RealmMetadata\n"} +{"Time":"2026-10-18T01:46:08.112324498Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_RealmMetadata","Output":"--- PASS: TestTransformer_RealmMetadata (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.112360206Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_RealmMetadata","Elapsed":0} +{"Time":"2026-10-18T01:46:08.112365923Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_EmitsMigrationEvent"} +{"Time":"2026-10-18T01:46:08.112369269Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_EmitsMigrationEvent","Output":"=== RUN TestTransformer_EmitsMigrationEvent\n"} +{"Time":"2026-10-18T01:46:08.112589882Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_EmitsMigrationEvent","Output":"--- PASS: TestTransformer_EmitsMigrationEvent (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.11261477Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_EmitsMigrationEvent","Elapsed":0} +{"Time":"2026-10-18T01:46:08.112629107Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_IncompatibleExport"} +{"Time":"2026-10-18T01:46:08.112634166Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_IncompatibleExport","Output":"=== RUN TestTransformer_ValidationReport_IncompatibleExport\n"} +{"Time":"2026-10-18T01:46:08.11286957Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_IncompatibleExport","Output":"--- PASS: TestTransformer_ValidationReport_IncompatibleExport (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.112882203Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_IncompatibleExport","Elapsed":0} +{"Time":"2026-10-18T01:46:08.112961348Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_CleanExport"} +{"Time":"2026-10-18T01:46:08.112970455Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_CleanExport","Output":"=== RUN TestTransformer_ValidationReport_CleanExport\n"} +{"Time":"2026-10-18T01:46:08.113192343Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_CleanExport","Output":"--- PASS: TestTransformer_ValidationReport_CleanExport (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.113205454Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransformer_ValidationReport_CleanExport","Elapsed":0} +{"Time":"2026-10-18T01:46:08.113286528Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_FlowsFollowDeclaredGrants"} +{"Time":"2026-10-18T01:46:08.113299314Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_FlowsFollowDeclaredGrants","Output":"=== RUN TestMapClient_FlowsFollowDeclaredGrants\n"} +{"Time":"2026-10-18T01:46:08.113546766Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_FlowsFollowDeclaredGrants","Output":"--- PASS: TestMapClient_FlowsFollowDeclaredGrants (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.11358352Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_FlowsFollowDeclaredGrants","Elapsed":0} +{"Time":"2026-10-18T01:46:08.113644837Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_CarriesProfileClaimsAndLifetime"} +{"Time":"2026-10-18T01:46:08.113652962Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_CarriesProfileClaimsAndLifetime","Output":"=== RUN TestMapClient_CarriesProfileClaimsAndLifetime\n"} +{"Time":"2026-10-18T01:46:08.113901748Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_CarriesProfileClaimsAndLifetime","Output":"--- PASS: TestMapClient_CarriesProfileClaimsAndLifetime (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.113994619Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestMapClient_CarriesProfileClaimsAndLifetime","Elapsed":0} +{"Time":"2026-10-18T01:46:08.114027983Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_NeverEmitsSecretValues"} +{"Time":"2026-10-18T01:46:08.114034503Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_NeverEmitsSecretValues","Output":"=== RUN TestTransform_NeverEmitsSecretValues\n"} +{"Time":"2026-10-18T01:46:08.115995625Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_NeverEmitsSecretValues","Output":"--- PASS: TestTransform_NeverEmitsSecretValues (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.116060116Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_NeverEmitsSecretValues","Elapsed":0} +{"Time":"2026-10-18T01:46:08.116072335Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_DerivesRolesAndScopes"} +{"Time":"2026-10-18T01:46:08.116077033Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_DerivesRolesAndScopes","Output":"=== RUN TestTransform_DerivesRolesAndScopes\n"} +{"Time":"2026-10-18T01:46:08.116395594Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_DerivesRolesAndScopes","Output":"--- PASS: TestTransform_DerivesRolesAndScopes (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.116427984Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestTransform_DerivesRolesAndScopes","Elapsed":0} +{"Time":"2026-10-18T01:46:08.116436641Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestUnpreservedReport_NamesEveryLimit"} +{"Time":"2026-10-18T01:46:08.116440969Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestUnpreservedReport_NamesEveryLimit","Output":"=== RUN TestUnpreservedReport_NamesEveryLimit\n"} +{"Time":"2026-10-18T01:46:08.116776095Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestUnpreservedReport_NamesEveryLimit","Output":"--- PASS: TestUnpreservedReport_NamesEveryLimit (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.116796501Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestUnpreservedReport_NamesEveryLimit","Elapsed":0} +{"Time":"2026-10-18T01:46:08.116808828Z","Action":"run","Package":"keycape/internal/migration/tokeycloak","Test":"TestValidationReportStaysSeparateFromUnpreserved"} +{"Time":"2026-10-18T01:46:08.116813922Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestValidationReportStaysSeparateFromUnpreserved","Output":"=== RUN TestValidationReportStaysSeparateFromUnpreserved\n"} +{"Time":"2026-10-18T01:46:08.117041219Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Test":"TestValidationReportStaysSeparateFromUnpreserved","Output":"--- PASS: TestValidationReportStaysSeparateFromUnpreserved (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.117069425Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Test":"TestValidationReportStaysSeparateFromUnpreserved","Elapsed":0} +{"Time":"2026-10-18T01:46:08.117198002Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:08.267158035Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/server","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/server (0.20s)\n"} +{"Time":"2026-10-18T01:46:08.267220119Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/server","Elapsed":0.2} +{"Time":"2026-10-18T01:46:08.267235557Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/invalid_token"} +{"Time":"2026-10-18T01:46:08.267245656Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/invalid_token","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/invalid_token\n"} +{"Time":"2026-10-18T01:46:08.381131669Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/invalid_token","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/invalid_token (0.11s)\n"} +{"Time":"2026-10-18T01:46:08.381185547Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/invalid_token","Elapsed":0.11} +{"Time":"2026-10-18T01:46:08.381197026Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/unrelated_refusal"} +{"Time":"2026-10-18T01:46:08.381202392Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/unrelated_refusal","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/unrelated_refusal\n"} +{"Time":"2026-10-18T01:46:08.477555078Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/unrelated_refusal","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/unrelated_refusal (0.10s)\n"} +{"Time":"2026-10-18T01:46:08.47759947Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/unrelated_refusal","Elapsed":0.1} +{"Time":"2026-10-18T01:46:08.477608108Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/malformed_refusal"} +{"Time":"2026-10-18T01:46:08.47762864Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/malformed_refusal","Output":"=== RUN TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/malformed_refusal\n"} +{"Time":"2026-10-18T01:46:08.584581531Z","Action":"start","Package":"keycape/internal/migration/toldap"} +{"Time":"2026-10-18T01:46:08.599677821Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_UserLDIF_RequiredAttributes"} +{"Time":"2026-10-18T01:46:08.599729084Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_UserLDIF_RequiredAttributes","Output":"=== RUN TestGenerator_UserLDIF_RequiredAttributes\n"} +{"Time":"2026-10-18T01:46:08.600024349Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_UserLDIF_RequiredAttributes","Output":"--- PASS: TestGenerator_UserLDIF_RequiredAttributes (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.600178705Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_UserLDIF_RequiredAttributes","Elapsed":0} +{"Time":"2026-10-18T01:46:08.60021353Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_GroupLDIF_WithMemberDNs"} +{"Time":"2026-10-18T01:46:08.600219643Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_GroupLDIF_WithMemberDNs","Output":"=== RUN TestGenerator_GroupLDIF_WithMemberDNs\n"} +{"Time":"2026-10-18T01:46:08.600440161Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_GroupLDIF_WithMemberDNs","Output":"--- PASS: TestGenerator_GroupLDIF_WithMemberDNs (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.600459043Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_GroupLDIF_WithMemberDNs","Elapsed":0} +{"Time":"2026-10-18T01:46:08.600529567Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_NoSAMAccountName"} +{"Time":"2026-10-18T01:46:08.600544383Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_NoSAMAccountName","Output":"=== RUN TestGenerator_Target_OpenLDAP_NoSAMAccountName\n"} +{"Time":"2026-10-18T01:46:08.600765215Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_NoSAMAccountName","Output":"--- PASS: TestGenerator_Target_OpenLDAP_NoSAMAccountName (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.600861517Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_NoSAMAccountName","Elapsed":0} +{"Time":"2026-10-18T01:46:08.600883465Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_HasNsUniqueIdIfAvailable"} +{"Time":"2026-10-18T01:46:08.600889633Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_HasNsUniqueIdIfAvailable","Output":"=== RUN TestGenerator_Target_389DS_HasNsUniqueIdIfAvailable\n"} +{"Time":"2026-10-18T01:46:08.601155099Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_HasNsUniqueIdIfAvailable","Output":"--- PASS: TestGenerator_Target_389DS_HasNsUniqueIdIfAvailable (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.601185613Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_HasNsUniqueIdIfAvailable","Elapsed":0} +{"Time":"2026-10-18T01:46:08.601208526Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_NoNsUniqueIdWhenAbsent"} +{"Time":"2026-10-18T01:46:08.601216606Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_NoNsUniqueIdWhenAbsent","Output":"=== RUN TestGenerator_Target_389DS_NoNsUniqueIdWhenAbsent\n"} +{"Time":"2026-10-18T01:46:08.601437152Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_NoNsUniqueIdWhenAbsent","Output":"--- PASS: TestGenerator_Target_389DS_NoNsUniqueIdWhenAbsent (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.601485458Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_389DS_NoNsUniqueIdWhenAbsent","Elapsed":0} +{"Time":"2026-10-18T01:46:08.601497847Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_SAMAccountNamePresent"} +{"Time":"2026-10-18T01:46:08.601503167Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_SAMAccountNamePresent","Output":"=== RUN TestGenerator_Target_AD_SAMAccountNamePresent\n"} +{"Time":"2026-10-18T01:46:08.601646696Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_SAMAccountNamePresent","Output":"--- PASS: TestGenerator_Target_AD_SAMAccountNamePresent (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.601674667Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_SAMAccountNamePresent","Elapsed":0} +{"Time":"2026-10-18T01:46:08.601689961Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_UsesCNInDN"} +{"Time":"2026-10-18T01:46:08.601695461Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_UsesCNInDN","Output":"=== RUN TestGenerator_Target_AD_UsesCNInDN\n"} +{"Time":"2026-10-18T01:46:08.601973607Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_UsesCNInDN","Output":"--- PASS: TestGenerator_Target_AD_UsesCNInDN (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.602006313Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_AD_UsesCNInDN","Elapsed":0} +{"Time":"2026-10-18T01:46:08.602023274Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_UsesUIDInDN"} +{"Time":"2026-10-18T01:46:08.602028522Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_UsesUIDInDN","Output":"=== RUN TestGenerator_Target_OpenLDAP_UsesUIDInDN\n"} +{"Time":"2026-10-18T01:46:08.602299644Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_UsesUIDInDN","Output":"--- PASS: TestGenerator_Target_OpenLDAP_UsesUIDInDN (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.602332575Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_Target_OpenLDAP_UsesUIDInDN","Elapsed":0} +{"Time":"2026-10-18T01:46:08.602429203Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmptyExport_StructuralEntriesOnly"} +{"Time":"2026-10-18T01:46:08.602447697Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmptyExport_StructuralEntriesOnly","Output":"=== RUN TestGenerator_EmptyExport_StructuralEntriesOnly\n"} +{"Time":"2026-10-18T01:46:08.602660947Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmptyExport_StructuralEntriesOnly","Output":"--- PASS: TestGenerator_EmptyExport_StructuralEntriesOnly (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.602695484Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmptyExport_StructuralEntriesOnly","Elapsed":0} +{"Time":"2026-10-18T01:46:08.602709118Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmitsMigrationEvent"} +{"Time":"2026-10-18T01:46:08.602713789Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmitsMigrationEvent","Output":"=== RUN TestGenerator_EmitsMigrationEvent\n"} +{"Time":"2026-10-18T01:46:08.602998585Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmitsMigrationEvent","Output":"--- PASS: TestGenerator_EmitsMigrationEvent (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.603029163Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_EmitsMigrationEvent","Elapsed":0} +{"Time":"2026-10-18T01:46:08.60306222Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationRunsOnOutput"} +{"Time":"2026-10-18T01:46:08.603069939Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationRunsOnOutput","Output":"=== RUN TestGenerator_ValidationRunsOnOutput\n"} +{"Time":"2026-10-18T01:46:08.60324568Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationRunsOnOutput","Output":"--- PASS: TestGenerator_ValidationRunsOnOutput (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.603271812Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationRunsOnOutput","Elapsed":0} +{"Time":"2026-10-18T01:46:08.603390623Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationFailsForInvalidData"} +{"Time":"2026-10-18T01:46:08.60340185Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationFailsForInvalidData","Output":"=== RUN TestGenerator_ValidationFailsForInvalidData\n"} +{"Time":"2026-10-18T01:46:08.603661556Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationFailsForInvalidData","Output":"--- PASS: TestGenerator_ValidationFailsForInvalidData (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.603676858Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestGenerator_ValidationFailsForInvalidData","Elapsed":0} +{"Time":"2026-10-18T01:46:08.60369482Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNsAreRewrittenIntoTheTargetLayout"} +{"Time":"2026-10-18T01:46:08.603702079Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNsAreRewrittenIntoTheTargetLayout","Output":"=== RUN TestMemberDNsAreRewrittenIntoTheTargetLayout\n"} +{"Time":"2026-10-18T01:46:08.603921523Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNsAreRewrittenIntoTheTargetLayout","Output":"--- PASS: TestMemberDNsAreRewrittenIntoTheTargetLayout (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.603941217Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNsAreRewrittenIntoTheTargetLayout","Elapsed":0} +{"Time":"2026-10-18T01:46:08.603952136Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNRewriteFollowsTargetNamingAttribute"} +{"Time":"2026-10-18T01:46:08.603965743Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNRewriteFollowsTargetNamingAttribute","Output":"=== RUN TestMemberDNRewriteFollowsTargetNamingAttribute\n"} +{"Time":"2026-10-18T01:46:08.604242221Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNRewriteFollowsTargetNamingAttribute","Output":"--- PASS: TestMemberDNRewriteFollowsTargetNamingAttribute (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.604432621Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestMemberDNRewriteFollowsTargetNamingAttribute","Elapsed":0} +{"Time":"2026-10-18T01:46:08.604461936Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestEmptyGroupsGetALoadablePlaceholderMember"} +{"Time":"2026-10-18T01:46:08.604469745Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestEmptyGroupsGetALoadablePlaceholderMember","Output":"=== RUN TestEmptyGroupsGetALoadablePlaceholderMember\n"} +{"Time":"2026-10-18T01:46:08.604480809Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestEmptyGroupsGetALoadablePlaceholderMember","Output":"--- PASS: TestEmptyGroupsGetALoadablePlaceholderMember (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.604489817Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestEmptyGroupsGetALoadablePlaceholderMember","Elapsed":0} +{"Time":"2026-10-18T01:46:08.6047032Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/malformed_refusal","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/malformed_refusal (0.13s)\n"} +{"Time":"2026-10-18T01:46:08.604763047Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals/predecessor/malformed_refusal","Elapsed":0.13} +{"Time":"2026-10-18T01:46:08.604777403Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals","Output":"--- PASS: TestVerifyClientDoesNotConfuseFailuresWithRefusals (1.78s)\n"} +{"Time":"2026-10-18T01:46:08.604785816Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestVerifyClientDoesNotConfuseFailuresWithRefusals","Elapsed":1.78} +{"Time":"2026-10-18T01:46:08.604800099Z","Action":"output","Package":"keycape/internal/authclient","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:08.604856971Z","Action":"run","Package":"keycape/internal/migration/toldap","Test":"TestNoPlaceholderWhenEveryGroupHasMembers"} +{"Time":"2026-10-18T01:46:08.604877837Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestNoPlaceholderWhenEveryGroupHasMembers","Output":"=== RUN TestNoPlaceholderWhenEveryGroupHasMembers\n"} +{"Time":"2026-10-18T01:46:08.604968938Z","Action":"output","Package":"keycape/internal/migration/toldap","Test":"TestNoPlaceholderWhenEveryGroupHasMembers","Output":"--- PASS: TestNoPlaceholderWhenEveryGroupHasMembers (0.00s)\n"} +{"Time":"2026-10-18T01:46:08.604978028Z","Action":"pass","Package":"keycape/internal/migration/toldap","Test":"TestNoPlaceholderWhenEveryGroupHasMembers","Elapsed":0} +{"Time":"2026-10-18T01:46:08.604984439Z","Action":"output","Package":"keycape/internal/migration/toldap","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:08.716890732Z","Action":"output","Package":"keycape/internal/migration/lldapexport","Output":"ok \tkeycape/internal/migration/lldapexport\t1.039s\n"} +{"Time":"2026-10-18T01:46:08.716947478Z","Action":"pass","Package":"keycape/internal/migration/lldapexport","Elapsed":1.039} +{"Time":"2026-10-18T01:46:09.120501404Z","Action":"output","Package":"keycape/internal/migration/tokeycloak","Output":"ok \tkeycape/internal/migration/tokeycloak\t1.030s\n"} +{"Time":"2026-10-18T01:46:09.120575914Z","Action":"pass","Package":"keycape/internal/migration/tokeycloak","Elapsed":1.03} +{"Time":"2026-10-18T01:46:09.357871155Z","Action":"start","Package":"keycape/internal/server/errors"} +{"Time":"2026-10-18T01:46:09.373951832Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PostConnect"} +{"Time":"2026-10-18T01:46:09.374015638Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PostConnect","Output":"=== RUN TestDefaultRegistry_DynamicClientRegistration_PostConnect\n"} +{"Time":"2026-10-18T01:46:09.375055008Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PostConnect","Output":"--- PASS: TestDefaultRegistry_DynamicClientRegistration_PostConnect (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.375107713Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PostConnect","Elapsed":0} +{"Time":"2026-10-18T01:46:09.375123755Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PathContainsRegistration"} +{"Time":"2026-10-18T01:46:09.375127479Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PathContainsRegistration","Output":"=== RUN TestDefaultRegistry_DynamicClientRegistration_PathContainsRegistration\n"} +{"Time":"2026-10-18T01:46:09.375523823Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PathContainsRegistration","Output":"--- PASS: TestDefaultRegistry_DynamicClientRegistration_PathContainsRegistration (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.375622021Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_DynamicClientRegistration_PathContainsRegistration","Elapsed":0} +{"Time":"2026-10-18T01:46:09.375638342Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_Token"} +{"Time":"2026-10-18T01:46:09.375642418Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_Token","Output":"=== RUN TestDefaultRegistry_ImplicitFlow_Token\n"} +{"Time":"2026-10-18T01:46:09.376099653Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_Token","Output":"--- PASS: TestDefaultRegistry_ImplicitFlow_Token (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.37623942Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_Token","Elapsed":0} +{"Time":"2026-10-18T01:46:09.376263262Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_IDToken"} +{"Time":"2026-10-18T01:46:09.376269312Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_IDToken","Output":"=== RUN TestDefaultRegistry_ImplicitFlow_IDToken\n"} +{"Time":"2026-10-18T01:46:09.376602034Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_IDToken","Output":"--- PASS: TestDefaultRegistry_ImplicitFlow_IDToken (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.376648328Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_ImplicitFlow_IDToken","Elapsed":0} +{"Time":"2026-10-18T01:46:09.376718474Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_WildcardRedirectURI"} +{"Time":"2026-10-18T01:46:09.376728573Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_WildcardRedirectURI","Output":"=== RUN TestDefaultRegistry_WildcardRedirectURI\n"} +{"Time":"2026-10-18T01:46:09.377057486Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_WildcardRedirectURI","Output":"--- PASS: TestDefaultRegistry_WildcardRedirectURI (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.3771612Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_WildcardRedirectURI","Elapsed":0} +{"Time":"2026-10-18T01:46:09.37719322Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_IdentityBroker"} +{"Time":"2026-10-18T01:46:09.377200325Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_IdentityBroker","Output":"=== RUN TestDefaultRegistry_IdentityBroker\n"} +{"Time":"2026-10-18T01:46:09.37757871Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_IdentityBroker","Output":"--- PASS: TestDefaultRegistry_IdentityBroker (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.377685456Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_IdentityBroker","Elapsed":0} +{"Time":"2026-10-18T01:46:09.37769919Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE"} +{"Time":"2026-10-18T01:46:09.377703599Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE","Output":"=== RUN TestDefaultRegistry_MissingPKCE\n"} +{"Time":"2026-10-18T01:46:09.378037526Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE","Output":"--- PASS: TestDefaultRegistry_MissingPKCE (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.378128976Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE","Elapsed":0} +{"Time":"2026-10-18T01:46:09.378150103Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE_WithCodeChallenge_PassesThrough"} +{"Time":"2026-10-18T01:46:09.378155135Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE_WithCodeChallenge_PassesThrough","Output":"=== RUN TestDefaultRegistry_MissingPKCE_WithCodeChallenge_PassesThrough\n"} +{"Time":"2026-10-18T01:46:09.378371146Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE_WithCodeChallenge_PassesThrough","Output":"--- PASS: TestDefaultRegistry_MissingPKCE_WithCodeChallenge_PassesThrough (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.378390362Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_MissingPKCE_WithCodeChallenge_PassesThrough","Elapsed":0} +{"Time":"2026-10-18T01:46:09.378472798Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_PKCEPlainMethod"} +{"Time":"2026-10-18T01:46:09.378483402Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_PKCEPlainMethod","Output":"=== RUN TestDefaultRegistry_PKCEPlainMethod\n"} +{"Time":"2026-10-18T01:46:09.378811512Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_PKCEPlainMethod","Output":"--- PASS: TestDefaultRegistry_PKCEPlainMethod (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.378829565Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_PKCEPlainMethod","Elapsed":0} +{"Time":"2026-10-18T01:46:09.378868745Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType"} +{"Time":"2026-10-18T01:46:09.378874029Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType","Output":"=== RUN TestDefaultRegistry_UnknownGrantType\n"} +{"Time":"2026-10-18T01:46:09.379210427Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType","Output":"--- PASS: TestDefaultRegistry_UnknownGrantType (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.379242077Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType","Elapsed":0} +{"Time":"2026-10-18T01:46:09.379253484Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType_AllowedTypes"} +{"Time":"2026-10-18T01:46:09.379257596Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType_AllowedTypes","Output":"=== RUN TestDefaultRegistry_UnknownGrantType_AllowedTypes\n"} +{"Time":"2026-10-18T01:46:09.379643245Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType_AllowedTypes","Output":"--- PASS: TestDefaultRegistry_UnknownGrantType_AllowedTypes (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.379678422Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_UnknownGrantType_AllowedTypes","Elapsed":0} +{"Time":"2026-10-18T01:46:09.379687565Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_NoMatchPassesThrough"} +{"Time":"2026-10-18T01:46:09.379691875Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_NoMatchPassesThrough","Output":"=== RUN TestDefaultRegistry_NoMatchPassesThrough\n"} +{"Time":"2026-10-18T01:46:09.380076028Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_NoMatchPassesThrough","Output":"--- PASS: TestDefaultRegistry_NoMatchPassesThrough (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.380120096Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestDefaultRegistry_NoMatchPassesThrough","Elapsed":0} +{"Time":"2026-10-18T01:46:09.380197933Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature"} +{"Time":"2026-10-18T01:46:09.380211765Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature","Output":"=== RUN TestRegistry_CustomFeature\n"} +{"Time":"2026-10-18T01:46:09.380678074Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature","Output":"--- PASS: TestRegistry_CustomFeature (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.380729463Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature","Elapsed":0} +{"Time":"2026-10-18T01:46:09.380739103Z","Action":"run","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature_NoMatch_PassesThrough"} +{"Time":"2026-10-18T01:46:09.380743837Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature_NoMatch_PassesThrough","Output":"=== RUN TestRegistry_CustomFeature_NoMatch_PassesThrough\n"} +{"Time":"2026-10-18T01:46:09.380974002Z","Action":"output","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature_NoMatch_PassesThrough","Output":"--- PASS: TestRegistry_CustomFeature_NoMatch_PassesThrough (0.00s)\n"} +{"Time":"2026-10-18T01:46:09.380996707Z","Action":"pass","Package":"keycape/internal/server/errors","Test":"TestRegistry_CustomFeature_NoMatch_PassesThrough","Elapsed":0} +{"Time":"2026-10-18T01:46:09.38100485Z","Action":"output","Package":"keycape/internal/server/errors","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:09.610574174Z","Action":"output","Package":"keycape/internal/migration/toldap","Output":"ok \tkeycape/internal/migration/toldap\t1.026s\n"} +{"Time":"2026-10-18T01:46:09.610632993Z","Action":"pass","Package":"keycape/internal/migration/toldap","Elapsed":1.026} +{"Time":"2026-10-18T01:46:09.612215676Z","Action":"output","Package":"keycape/internal/authclient","Output":"ok \tkeycape/internal/authclient\t4.474s\n"} +{"Time":"2026-10-18T01:46:09.612289356Z","Action":"pass","Package":"keycape/internal/authclient","Elapsed":4.475} +{"Time":"2026-10-18T01:46:13.970341129Z","Action":"output","Package":"keycape/internal/server/errors","Output":"ok \tkeycape/internal/server/errors\t1.030s\n"} +{"Time":"2026-10-18T01:46:13.970415395Z","Action":"pass","Package":"keycape/internal/server/errors","Elapsed":1.03} +{"Time":"2026-10-18T01:46:15.832455969Z","Action":"start","Package":"keycape/internal/server/oidc"} +{"Time":"2026-10-18T01:46:15.832513971Z","Action":"start","Package":"keycape/internal/server/policy"} +{"Time":"2026-10-18T01:46:15.832647321Z","Action":"start","Package":"keycape/internal/server/readiness"} +{"Time":"2026-10-18T01:46:15.849853823Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestReadyWhenEveryCheckPasses"} +{"Time":"2026-10-18T01:46:15.849911443Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestReadyWhenEveryCheckPasses","Output":"=== RUN TestReadyWhenEveryCheckPasses\n"} +{"Time":"2026-10-18T01:46:15.851312859Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestReadyWhenEveryCheckPasses","Output":"--- PASS: TestReadyWhenEveryCheckPasses (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.851379064Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestReadyWhenEveryCheckPasses","Elapsed":0} +{"Time":"2026-10-18T01:46:15.851397306Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestNotReadyWhenAnyCheckFails"} +{"Time":"2026-10-18T01:46:15.851403185Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestNotReadyWhenAnyCheckFails","Output":"=== RUN TestNotReadyWhenAnyCheckFails\n"} +{"Time":"2026-10-18T01:46:15.85167721Z","Action":"run","Package":"keycape/internal/server/policy","Test":"TestReviewedApplyReplayRestartRollback"} +{"Time":"2026-10-18T01:46:15.851717959Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestReviewedApplyReplayRestartRollback","Output":"=== RUN TestReviewedApplyReplayRestartRollback\n"} +{"Time":"2026-10-18T01:46:15.852227037Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestNotReadyWhenAnyCheckFails","Output":"--- PASS: TestNotReadyWhenAnyCheckFails (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.852278003Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestNotReadyWhenAnyCheckFails","Elapsed":0} +{"Time":"2026-10-18T01:46:15.852319879Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestNilProbeIsNotReady"} +{"Time":"2026-10-18T01:46:15.852331989Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestNilProbeIsNotReady","Output":"=== RUN TestNilProbeIsNotReady\n"} +{"Time":"2026-10-18T01:46:15.852655407Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestNilProbeIsNotReady","Output":"--- PASS: TestNilProbeIsNotReady (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.852689961Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestNilProbeIsNotReady","Elapsed":0} +{"Time":"2026-10-18T01:46:15.852703378Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestFailureDetailIsNotDisclosed"} +{"Time":"2026-10-18T01:46:15.852709204Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestFailureDetailIsNotDisclosed","Output":"=== RUN TestFailureDetailIsNotDisclosed\n"} +{"Time":"2026-10-18T01:46:15.853084979Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestFailureDetailIsNotDisclosed","Output":"--- PASS: TestFailureDetailIsNotDisclosed (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.853128443Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestFailureDetailIsNotDisclosed","Elapsed":0} +{"Time":"2026-10-18T01:46:15.853137297Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestResultsAreCachedWithinTheTTL"} +{"Time":"2026-10-18T01:46:15.853145296Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestResultsAreCachedWithinTheTTL","Output":"=== RUN TestResultsAreCachedWithinTheTTL\n"} +{"Time":"2026-10-18T01:46:15.854217741Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestResultsAreCachedWithinTheTTL","Output":"--- PASS: TestResultsAreCachedWithinTheTTL (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.854285253Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestResultsAreCachedWithinTheTTL","Elapsed":0} +{"Time":"2026-10-18T01:46:15.854301475Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestSlowProbeTimesOutRatherThanBlocking"} +{"Time":"2026-10-18T01:46:15.854306958Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestSlowProbeTimesOutRatherThanBlocking","Output":"=== RUN TestSlowProbeTimesOutRatherThanBlocking\n"} +{"Time":"2026-10-18T01:46:15.854737532Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestRecoveryStripsCredentials"} +{"Time":"2026-10-18T01:46:15.854774881Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestRecoveryStripsCredentials","Output":"=== RUN TestRecoveryStripsCredentials\n"} +{"Time":"2026-10-18T01:46:15.854730198Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestReviewedApplyReplayRestartRollback","Output":"--- PASS: TestReviewedApplyReplayRestartRollback (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.854828362Z","Action":"pass","Package":"keycape/internal/server/policy","Test":"TestReviewedApplyReplayRestartRollback","Elapsed":0} +{"Time":"2026-10-18T01:46:15.854843962Z","Action":"run","Package":"keycape/internal/server/policy","Test":"TestStaleExpiredUnsupportedAndAlteredRequests"} +{"Time":"2026-10-18T01:46:15.854848498Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestStaleExpiredUnsupportedAndAlteredRequests","Output":"=== RUN TestStaleExpiredUnsupportedAndAlteredRequests\n"} +{"Time":"2026-10-18T01:46:15.855257631Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestRecoveryStripsCredentials","Output":"--- PASS: TestRecoveryStripsCredentials (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.855295224Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestRecoveryStripsCredentials","Elapsed":0} +{"Time":"2026-10-18T01:46:15.855440537Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestSharedLogoutRequiresConfirmationAndFixedReturn"} +{"Time":"2026-10-18T01:46:15.855454775Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSharedLogoutRequiresConfirmationAndFixedReturn","Output":"=== RUN TestSharedLogoutRequiresConfirmationAndFixedReturn\n"} +{"Time":"2026-10-18T01:46:15.856225486Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestStaleExpiredUnsupportedAndAlteredRequests","Output":"--- PASS: TestStaleExpiredUnsupportedAndAlteredRequests (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.856292033Z","Action":"pass","Package":"keycape/internal/server/policy","Test":"TestStaleExpiredUnsupportedAndAlteredRequests","Elapsed":0} +{"Time":"2026-10-18T01:46:15.856306938Z","Action":"run","Package":"keycape/internal/server/policy","Test":"TestWriteFailureDoesNotChangeEffectivePolicyAndSingleWriter"} +{"Time":"2026-10-18T01:46:15.856312096Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestWriteFailureDoesNotChangeEffectivePolicyAndSingleWriter","Output":"=== RUN TestWriteFailureDoesNotChangeEffectivePolicyAndSingleWriter\n"} +{"Time":"2026-10-18T01:46:15.857067532Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestWriteFailureDoesNotChangeEffectivePolicyAndSingleWriter","Output":"--- PASS: TestWriteFailureDoesNotChangeEffectivePolicyAndSingleWriter (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.857127208Z","Action":"pass","Package":"keycape/internal/server/policy","Test":"TestWriteFailureDoesNotChangeEffectivePolicyAndSingleWriter","Elapsed":0} +{"Time":"2026-10-18T01:46:15.857138092Z","Action":"run","Package":"keycape/internal/server/policy","Test":"TestCorruptPersistenceFailsClosed"} +{"Time":"2026-10-18T01:46:15.857143989Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestCorruptPersistenceFailsClosed","Output":"=== RUN TestCorruptPersistenceFailsClosed\n"} +{"Time":"2026-10-18T01:46:15.857168119Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSharedLogoutRequiresConfirmationAndFixedReturn","Output":"--- PASS: TestSharedLogoutRequiresConfirmationAndFixedReturn (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.857179165Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestSharedLogoutRequiresConfirmationAndFixedReturn","Elapsed":0} +{"Time":"2026-10-18T01:46:15.857186929Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAssuranceReportsAuthenticationTimeNotMintTime"} +{"Time":"2026-10-18T01:46:15.857191744Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAssuranceReportsAuthenticationTimeNotMintTime","Output":"=== RUN TestAssuranceReportsAuthenticationTimeNotMintTime\n"} +{"Time":"2026-10-18T01:46:15.857506356Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAssuranceReportsAuthenticationTimeNotMintTime","Output":"--- PASS: TestAssuranceReportsAuthenticationTimeNotMintTime (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.857545942Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAssuranceReportsAuthenticationTimeNotMintTime","Elapsed":0} +{"Time":"2026-10-18T01:46:15.8575555Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAssuranceFallsBackToMintTimeWhenAuthTimeIsUnset"} +{"Time":"2026-10-18T01:46:15.857560784Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAssuranceFallsBackToMintTimeWhenAuthTimeIsUnset","Output":"=== RUN TestAssuranceFallsBackToMintTimeWhenAuthTimeIsUnset\n"} +{"Time":"2026-10-18T01:46:15.857770625Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAssuranceFallsBackToMintTimeWhenAuthTimeIsUnset","Output":"--- PASS: TestAssuranceFallsBackToMintTimeWhenAuthTimeIsUnset (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.857796838Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAssuranceFallsBackToMintTimeWhenAuthTimeIsUnset","Elapsed":0} +{"Time":"2026-10-18T01:46:15.8578081Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAssuranceCarriesTheDocumentedShape"} +{"Time":"2026-10-18T01:46:15.857813096Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAssuranceCarriesTheDocumentedShape","Output":"=== RUN TestAssuranceCarriesTheDocumentedShape\n"} +{"Time":"2026-10-18T01:46:15.857986299Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAssuranceCarriesTheDocumentedShape","Output":"--- PASS: TestAssuranceCarriesTheDocumentedShape (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.858016714Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAssuranceCarriesTheDocumentedShape","Elapsed":0} +{"Time":"2026-10-18T01:46:15.858028085Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantBindsTheZoneAClientDeclares"} +{"Time":"2026-10-18T01:46:15.858032449Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantBindsTheZoneAClientDeclares","Output":"=== RUN TestHumanTenantBindsTheZoneAClientDeclares\n"} +{"Time":"2026-10-18T01:46:15.858053532Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestCorruptPersistenceFailsClosed","Output":"--- PASS: TestCorruptPersistenceFailsClosed (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.858065994Z","Action":"pass","Package":"keycape/internal/server/policy","Test":"TestCorruptPersistenceFailsClosed","Elapsed":0} +{"Time":"2026-10-18T01:46:15.858219864Z","Action":"run","Package":"keycape/internal/server/policy","Test":"TestHTTPRequiresSignedFreshPlatformMFA"} +{"Time":"2026-10-18T01:46:15.858234409Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestHTTPRequiresSignedFreshPlatformMFA","Output":"=== RUN TestHTTPRequiresSignedFreshPlatformMFA\n"} +{"Time":"2026-10-18T01:46:15.858226037Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantBindsTheZoneAClientDeclares","Output":"--- PASS: TestHumanTenantBindsTheZoneAClientDeclares (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.858247093Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantBindsTheZoneAClientDeclares","Elapsed":0} +{"Time":"2026-10-18T01:46:15.858265136Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared"} +{"Time":"2026-10-18T01:46:15.85826996Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared","Output":"=== RUN TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared\n"} +{"Time":"2026-10-18T01:46:15.858477496Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/no_client_at_all"} +{"Time":"2026-10-18T01:46:15.858496188Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/no_client_at_all","Output":"=== RUN TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/no_client_at_all\n"} +{"Time":"2026-10-18T01:46:15.858759591Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/no_client_at_all","Output":"--- PASS: TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/no_client_at_all (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.858800394Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/no_client_at_all","Elapsed":0} +{"Time":"2026-10-18T01:46:15.858816238Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/client_declares_nothing"} +{"Time":"2026-10-18T01:46:15.858821648Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/client_declares_nothing","Output":"=== RUN TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/client_declares_nothing\n"} +{"Time":"2026-10-18T01:46:15.859047779Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/client_declares_nothing","Output":"--- PASS: TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/client_declares_nothing (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.859088384Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/client_declares_nothing","Elapsed":0} +{"Time":"2026-10-18T01:46:15.859100665Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/directory_answer_wins"} +{"Time":"2026-10-18T01:46:15.859111206Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/directory_answer_wins","Output":"=== RUN TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/directory_answer_wins\n"} +{"Time":"2026-10-18T01:46:15.859258617Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/directory_answer_wins","Output":"--- PASS: TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/directory_answer_wins (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.859282306Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared/directory_answer_wins","Elapsed":0} +{"Time":"2026-10-18T01:46:15.859454731Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared","Output":"--- PASS: TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.859474398Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantKeepsDirectoryAnswerWhenNoClientTenantIsDeclared","Elapsed":0} +{"Time":"2026-10-18T01:46:15.85948373Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantRefusesToRelabelAPlacedUser"} +{"Time":"2026-10-18T01:46:15.859488654Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantRefusesToRelabelAPlacedUser","Output":"=== RUN TestHumanTenantRefusesToRelabelAPlacedUser\n"} +{"Time":"2026-10-18T01:46:15.859653715Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantRefusesToRelabelAPlacedUser","Output":"--- PASS: TestHumanTenantRefusesToRelabelAPlacedUser (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.859670297Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantRefusesToRelabelAPlacedUser","Elapsed":0} +{"Time":"2026-10-18T01:46:15.859785439Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantAcceptsAgreementBetweenClientAndDirectory"} +{"Time":"2026-10-18T01:46:15.859797946Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantAcceptsAgreementBetweenClientAndDirectory","Output":"=== RUN TestHumanTenantAcceptsAgreementBetweenClientAndDirectory\n"} +{"Time":"2026-10-18T01:46:15.860001402Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantAcceptsAgreementBetweenClientAndDirectory","Output":"--- PASS: TestHumanTenantAcceptsAgreementBetweenClientAndDirectory (0.00s)\n"} +{"Time":"2026-10-18T01:46:15.860018453Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantAcceptsAgreementBetweenClientAndDirectory","Elapsed":0} +{"Time":"2026-10-18T01:46:15.860038156Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPrincipalTypeIsNeverSuppliedByConfiguration"} +{"Time":"2026-10-18T01:46:15.860182973Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPrincipalTypeIsNeverSuppliedByConfiguration","Output":"=== RUN TestPrincipalTypeIsNeverSuppliedByConfiguration\n"} +{"Time":"2026-10-18T01:46:15.906454605Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestSlowProbeTimesOutRatherThanBlocking","Output":"--- PASS: TestSlowProbeTimesOutRatherThanBlocking (0.05s)\n"} +{"Time":"2026-10-18T01:46:15.906511215Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestSlowProbeTimesOutRatherThanBlocking","Elapsed":0.05} +{"Time":"2026-10-18T01:46:15.906828523Z","Action":"run","Package":"keycape/internal/server/readiness","Test":"TestChecksRunConcurrently"} +{"Time":"2026-10-18T01:46:15.90684837Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestChecksRunConcurrently","Output":"=== RUN TestChecksRunConcurrently\n"} +{"Time":"2026-10-18T01:46:16.00624071Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPrincipalTypeIsNeverSuppliedByConfiguration","Output":"--- PASS: TestPrincipalTypeIsNeverSuppliedByConfiguration (0.15s)\n"} +{"Time":"2026-10-18T01:46:16.006323641Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPrincipalTypeIsNeverSuppliedByConfiguration","Elapsed":0.15} +{"Time":"2026-10-18T01:46:16.006341685Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones"} +{"Time":"2026-10-18T01:46:16.00636081Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Output":"=== RUN TestTokenStatusExactExpiryCapacityAndTombstones\n"} +{"Time":"2026-10-18T01:46:16.006964759Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Output":"--- PASS: TestTokenStatusExactExpiryCapacityAndTombstones (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.007001269Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusExactExpiryCapacityAndTombstones","Elapsed":0} +{"Time":"2026-10-18T01:46:16.007016518Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke"} +{"Time":"2026-10-18T01:46:16.007030945Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":"=== RUN TestTokenStatusConcurrentIssueReadAndRevoke\n"} +{"Time":"2026-10-18T01:46:16.02468303Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Output":"--- PASS: TestTokenStatusConcurrentIssueReadAndRevoke (0.02s)\n"} +{"Time":"2026-10-18T01:46:16.024734771Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusConcurrentIssueReadAndRevoke","Elapsed":0.02} +{"Time":"2026-10-18T01:46:16.024746232Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID"} +{"Time":"2026-10-18T01:46:16.024750948Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID","Output":"=== RUN TestServiceAudienceIsStaticAndDefaultsToClientID\n"} +{"Time":"2026-10-18T01:46:16.024983359Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/#00"} +{"Time":"2026-10-18T01:46:16.024996812Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/#00","Output":"=== RUN TestServiceAudienceIsStaticAndDefaultsToClientID/#00\n"} +{"Time":"2026-10-18T01:46:16.058670078Z","Action":"output","Package":"keycape/internal/server/readiness","Test":"TestChecksRunConcurrently","Output":"--- PASS: TestChecksRunConcurrently (0.15s)\n"} +{"Time":"2026-10-18T01:46:16.058777618Z","Action":"pass","Package":"keycape/internal/server/readiness","Test":"TestChecksRunConcurrently","Elapsed":0.15} +{"Time":"2026-10-18T01:46:16.05879494Z","Action":"output","Package":"keycape/internal/server/readiness","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:16.093361626Z","Action":"start","Package":"keycape/internal/server/telemetry"} +{"Time":"2026-10-18T01:46:16.1119113Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_DoesNotPanic"} +{"Time":"2026-10-18T01:46:16.112017505Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_DoesNotPanic","Output":"=== RUN TestNoopEmitter_DoesNotPanic\n"} +{"Time":"2026-10-18T01:46:16.112136335Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_DoesNotPanic","Output":"--- PASS: TestNoopEmitter_DoesNotPanic (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.112180465Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_DoesNotPanic","Elapsed":0} +{"Time":"2026-10-18T01:46:16.112290241Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_ImplementsInterface"} +{"Time":"2026-10-18T01:46:16.112303776Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_ImplementsInterface","Output":"=== RUN TestNoopEmitter_ImplementsInterface\n"} +{"Time":"2026-10-18T01:46:16.112499622Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_ImplementsInterface","Output":"--- PASS: TestNoopEmitter_ImplementsInterface (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.112619421Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestNoopEmitter_ImplementsInterface","Elapsed":0} +{"Time":"2026-10-18T01:46:16.112651053Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_WritesJSON"} +{"Time":"2026-10-18T01:46:16.112660846Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_WritesJSON","Output":"=== RUN TestLogEmitter_WritesJSON\n"} +{"Time":"2026-10-18T01:46:16.113128061Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_WritesJSON","Output":"--- PASS: TestLogEmitter_WritesJSON (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.113167637Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_WritesJSON","Elapsed":0} +{"Time":"2026-10-18T01:46:16.113183742Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ContainsEventFields"} +{"Time":"2026-10-18T01:46:16.113191425Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ContainsEventFields","Output":"=== RUN TestLogEmitter_ContainsEventFields\n"} +{"Time":"2026-10-18T01:46:16.113448903Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ContainsEventFields","Output":"--- PASS: TestLogEmitter_ContainsEventFields (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.113491456Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ContainsEventFields","Elapsed":0} +{"Time":"2026-10-18T01:46:16.113578418Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_EventTypeValue"} +{"Time":"2026-10-18T01:46:16.113592007Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_EventTypeValue","Output":"=== RUN TestLogEmitter_EventTypeValue\n"} +{"Time":"2026-10-18T01:46:16.113861601Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_EventTypeValue","Output":"--- PASS: TestLogEmitter_EventTypeValue (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.114006183Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_EventTypeValue","Elapsed":0} +{"Time":"2026-10-18T01:46:16.11402768Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ImplementsInterface"} +{"Time":"2026-10-18T01:46:16.114034398Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ImplementsInterface","Output":"=== RUN TestLogEmitter_ImplementsInterface\n"} +{"Time":"2026-10-18T01:46:16.114387252Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ImplementsInterface","Output":"--- PASS: TestLogEmitter_ImplementsInterface (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.114412924Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestLogEmitter_ImplementsInterface","Elapsed":0} +{"Time":"2026-10-18T01:46:16.114545455Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_FansOut"} +{"Time":"2026-10-18T01:46:16.114567159Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_FansOut","Output":"=== RUN TestMultiEmitter_FansOut\n"} +{"Time":"2026-10-18T01:46:16.114805467Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_FansOut","Output":"--- PASS: TestMultiEmitter_FansOut (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.11484416Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_FansOut","Elapsed":0} +{"Time":"2026-10-18T01:46:16.114863475Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_EmptyIsNoop"} +{"Time":"2026-10-18T01:46:16.114876153Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_EmptyIsNoop","Output":"=== RUN TestMultiEmitter_EmptyIsNoop\n"} +{"Time":"2026-10-18T01:46:16.115094398Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_EmptyIsNoop","Output":"--- PASS: TestMultiEmitter_EmptyIsNoop (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.115110467Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_EmptyIsNoop","Elapsed":0} +{"Time":"2026-10-18T01:46:16.115119835Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_ImplementsInterface"} +{"Time":"2026-10-18T01:46:16.1151253Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_ImplementsInterface","Output":"=== RUN TestMultiEmitter_ImplementsInterface\n"} +{"Time":"2026-10-18T01:46:16.115308052Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_ImplementsInterface","Output":"--- PASS: TestMultiEmitter_ImplementsInterface (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.11532331Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestMultiEmitter_ImplementsInterface","Elapsed":0} +{"Time":"2026-10-18T01:46:16.115333346Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestWithEmitter_RoundTrip"} +{"Time":"2026-10-18T01:46:16.115338819Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestWithEmitter_RoundTrip","Output":"=== RUN TestWithEmitter_RoundTrip\n"} +{"Time":"2026-10-18T01:46:16.11552462Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestWithEmitter_RoundTrip","Output":"--- PASS: TestWithEmitter_RoundTrip (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.115539985Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestWithEmitter_RoundTrip","Elapsed":0} +{"Time":"2026-10-18T01:46:16.115553845Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestEmitterFromContext_FallsBackToNoop"} +{"Time":"2026-10-18T01:46:16.115559417Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestEmitterFromContext_FallsBackToNoop","Output":"=== RUN TestEmitterFromContext_FallsBackToNoop\n"} +{"Time":"2026-10-18T01:46:16.115770394Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestEmitterFromContext_FallsBackToNoop","Output":"--- PASS: TestEmitterFromContext_FallsBackToNoop (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.115786075Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestEmitterFromContext_FallsBackToNoop","Elapsed":0} +{"Time":"2026-10-18T01:46:16.115795669Z","Action":"run","Package":"keycape/internal/server/telemetry","Test":"TestEventTypeConstants"} +{"Time":"2026-10-18T01:46:16.115801161Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestEventTypeConstants","Output":"=== RUN TestEventTypeConstants\n"} +{"Time":"2026-10-18T01:46:16.115958268Z","Action":"output","Package":"keycape/internal/server/telemetry","Test":"TestEventTypeConstants","Output":"--- PASS: TestEventTypeConstants (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.115973742Z","Action":"pass","Package":"keycape/internal/server/telemetry","Test":"TestEventTypeConstants","Elapsed":0} +{"Time":"2026-10-18T01:46:16.115982407Z","Action":"output","Package":"keycape/internal/server/telemetry","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:16.237264949Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/#00","Output":"--- PASS: TestServiceAudienceIsStaticAndDefaultsToClientID/#00 (0.21s)\n"} +{"Time":"2026-10-18T01:46:16.237337943Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/#00","Elapsed":0.21} +{"Time":"2026-10-18T01:46:16.237349846Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/approval-engine"} +{"Time":"2026-10-18T01:46:16.237354679Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/approval-engine","Output":"=== RUN TestServiceAudienceIsStaticAndDefaultsToClientID/approval-engine\n"} +{"Time":"2026-10-18T01:46:16.261162619Z","Action":"output","Package":"keycape/internal/server/policy","Test":"TestHTTPRequiresSignedFreshPlatformMFA","Output":"--- PASS: TestHTTPRequiresSignedFreshPlatformMFA (0.40s)\n"} +{"Time":"2026-10-18T01:46:16.261218905Z","Action":"pass","Package":"keycape/internal/server/policy","Test":"TestHTTPRequiresSignedFreshPlatformMFA","Elapsed":0.4} +{"Time":"2026-10-18T01:46:16.261230366Z","Action":"output","Package":"keycape/internal/server/policy","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:16.285493966Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/approval-engine","Output":"--- PASS: TestServiceAudienceIsStaticAndDefaultsToClientID/approval-engine (0.05s)\n"} +{"Time":"2026-10-18T01:46:16.28555577Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID/approval-engine","Elapsed":0.05} +{"Time":"2026-10-18T01:46:16.285567321Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID","Output":"--- PASS: TestServiceAudienceIsStaticAndDefaultsToClientID (0.26s)\n"} +{"Time":"2026-10-18T01:46:16.285572821Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestServiceAudienceIsStaticAndDefaultsToClientID","Elapsed":0.26} +{"Time":"2026-10-18T01:46:16.285577727Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanResourceAudiencePreservesIDTokenAudience"} +{"Time":"2026-10-18T01:46:16.285582136Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanResourceAudiencePreservesIDTokenAudience","Output":"=== RUN TestHumanResourceAudiencePreservesIDTokenAudience\n"} +{"Time":"2026-10-18T01:46:16.317530249Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanResourceAudiencePreservesIDTokenAudience","Output":"--- PASS: TestHumanResourceAudiencePreservesIDTokenAudience (0.03s)\n"} +{"Time":"2026-10-18T01:46:16.317592865Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanResourceAudiencePreservesIDTokenAudience","Elapsed":0.03} +{"Time":"2026-10-18T01:46:16.317603514Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanExcessScopeRejectedBeforeAuthentication"} +{"Time":"2026-10-18T01:46:16.317609989Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanExcessScopeRejectedBeforeAuthentication","Output":"=== RUN TestHumanExcessScopeRejectedBeforeAuthentication\n"} +{"Time":"2026-10-18T01:46:16.317810433Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanExcessScopeRejectedBeforeAuthentication","Output":"--- PASS: TestHumanExcessScopeRejectedBeforeAuthentication (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.317827151Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanExcessScopeRejectedBeforeAuthentication","Elapsed":0} +{"Time":"2026-10-18T01:46:16.317834401Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenRejectsScopeRemovedAfterAuthorization"} +{"Time":"2026-10-18T01:46:16.317840289Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenRejectsScopeRemovedAfterAuthorization","Output":"=== RUN TestTokenRejectsScopeRemovedAfterAuthorization\n"} +{"Time":"2026-10-18T01:46:16.57697237Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenRejectsScopeRemovedAfterAuthorization","Output":"--- PASS: TestTokenRejectsScopeRemovedAfterAuthorization (0.26s)\n"} +{"Time":"2026-10-18T01:46:16.577031649Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenRejectsScopeRemovedAfterAuthorization","Elapsed":0.26} +{"Time":"2026-10-18T01:46:16.577050225Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ValidRequest_RedirectsToAuthelia"} +{"Time":"2026-10-18T01:46:16.577057664Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ValidRequest_RedirectsToAuthelia","Output":"=== RUN TestAuthorizeHandler_ValidRequest_RedirectsToAuthelia\n"} +{"Time":"2026-10-18T01:46:16.577237432Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ValidRequest_RedirectsToAuthelia","Output":"--- PASS: TestAuthorizeHandler_ValidRequest_RedirectsToAuthelia (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.577255538Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ValidRequest_RedirectsToAuthelia","Elapsed":0} +{"Time":"2026-10-18T01:46:16.577262869Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_BootstrapConsoleRedirectURI_RedirectsToAuthelia"} +{"Time":"2026-10-18T01:46:16.577268866Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_BootstrapConsoleRedirectURI_RedirectsToAuthelia","Output":"=== RUN TestAuthorizeHandler_BootstrapConsoleRedirectURI_RedirectsToAuthelia\n"} +{"Time":"2026-10-18T01:46:16.577495055Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_BootstrapConsoleRedirectURI_RedirectsToAuthelia","Output":"--- PASS: TestAuthorizeHandler_BootstrapConsoleRedirectURI_RedirectsToAuthelia (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.577511727Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_BootstrapConsoleRedirectURI_RedirectsToAuthelia","Elapsed":0} +{"Time":"2026-10-18T01:46:16.577519131Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_EmitsAuthStart"} +{"Time":"2026-10-18T01:46:16.577525452Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_EmitsAuthStart","Output":"=== RUN TestAuthorizeHandler_EmitsAuthStart\n"} +{"Time":"2026-10-18T01:46:16.577727709Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_EmitsAuthStart","Output":"--- PASS: TestAuthorizeHandler_EmitsAuthStart (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.577745139Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_EmitsAuthStart","Elapsed":0} +{"Time":"2026-10-18T01:46:16.577752461Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingCodeChallenge_InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:16.577758205Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingCodeChallenge_InvalidProfileUsage","Output":"=== RUN TestAuthorizeHandler_MissingCodeChallenge_InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:16.577994024Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingCodeChallenge_InvalidProfileUsage","Output":"--- PASS: TestAuthorizeHandler_MissingCodeChallenge_InvalidProfileUsage (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.57801058Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingCodeChallenge_InvalidProfileUsage","Elapsed":0} +{"Time":"2026-10-18T01:46:16.578017775Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WildcardRedirectURI_RejectedForSafety"} +{"Time":"2026-10-18T01:46:16.578023583Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WildcardRedirectURI_RejectedForSafety","Output":"=== RUN TestAuthorizeHandler_WildcardRedirectURI_RejectedForSafety\n"} +{"Time":"2026-10-18T01:46:16.578216912Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WildcardRedirectURI_RejectedForSafety","Output":"--- PASS: TestAuthorizeHandler_WildcardRedirectURI_RejectedForSafety (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.578234658Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WildcardRedirectURI_RejectedForSafety","Elapsed":0} +{"Time":"2026-10-18T01:46:16.578241989Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownClient_InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:16.578247697Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownClient_InvalidProfileUsage","Output":"=== RUN TestAuthorizeHandler_UnknownClient_InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:16.578496032Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownClient_InvalidProfileUsage","Output":"--- PASS: TestAuthorizeHandler_UnknownClient_InvalidProfileUsage (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.578522417Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownClient_InvalidProfileUsage","Elapsed":0} +{"Time":"2026-10-18T01:46:16.578530009Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WrongResponseType_FeatureNotSupported"} +{"Time":"2026-10-18T01:46:16.578535898Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WrongResponseType_FeatureNotSupported","Output":"=== RUN TestAuthorizeHandler_WrongResponseType_FeatureNotSupported\n"} +{"Time":"2026-10-18T01:46:16.578713772Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WrongResponseType_FeatureNotSupported","Output":"--- PASS: TestAuthorizeHandler_WrongResponseType_FeatureNotSupported (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.578741229Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_WrongResponseType_FeatureNotSupported","Elapsed":0} +{"Time":"2026-10-18T01:46:16.578750932Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingOpenIDScope_InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:16.578756847Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingOpenIDScope_InvalidProfileUsage","Output":"=== RUN TestAuthorizeHandler_MissingOpenIDScope_InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:16.579027058Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingOpenIDScope_InvalidProfileUsage","Output":"--- PASS: TestAuthorizeHandler_MissingOpenIDScope_InvalidProfileUsage (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.579043542Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_MissingOpenIDScope_InvalidProfileUsage","Elapsed":0} +{"Time":"2026-10-18T01:46:16.579050882Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_PlainCodeChallengeMethod_RejectedForSafety"} +{"Time":"2026-10-18T01:46:16.579056562Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_PlainCodeChallengeMethod_RejectedForSafety","Output":"=== RUN TestAuthorizeHandler_PlainCodeChallengeMethod_RejectedForSafety\n"} +{"Time":"2026-10-18T01:46:16.579257007Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_PlainCodeChallengeMethod_RejectedForSafety","Output":"--- PASS: TestAuthorizeHandler_PlainCodeChallengeMethod_RejectedForSafety (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.57927386Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_PlainCodeChallengeMethod_RejectedForSafety","Elapsed":0} +{"Time":"2026-10-18T01:46:16.579283463Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownRedirectURI_InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:16.579303915Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownRedirectURI_InvalidProfileUsage","Output":"=== RUN TestAuthorizeHandler_UnknownRedirectURI_InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:16.579594243Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownRedirectURI_InvalidProfileUsage","Output":"--- PASS: TestAuthorizeHandler_UnknownRedirectURI_InvalidProfileUsage (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.579611204Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_UnknownRedirectURI_InvalidProfileUsage","Elapsed":0} +{"Time":"2026-10-18T01:46:16.579618427Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_Success_RedirectsWithCode"} +{"Time":"2026-10-18T01:46:16.579624496Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_Success_RedirectsWithCode","Output":"=== RUN TestAuthorizeCallback_Success_RedirectsWithCode\n"} +{"Time":"2026-10-18T01:46:16.583242111Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_Success_RedirectsWithCode","Output":"--- PASS: TestAuthorizeCallback_Success_RedirectsWithCode (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.583292562Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_Success_RedirectsWithCode","Elapsed":0} +{"Time":"2026-10-18T01:46:16.583310453Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFAFailed_AuthFailure"} +{"Time":"2026-10-18T01:46:16.583316945Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFAFailed_AuthFailure","Output":"=== RUN TestAuthorizeCallback_MFAFailed_AuthFailure\n"} +{"Time":"2026-10-18T01:46:16.583645245Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFAFailed_AuthFailure","Output":"--- PASS: TestAuthorizeCallback_MFAFailed_AuthFailure (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.583661675Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFAFailed_AuthFailure","Elapsed":0} +{"Time":"2026-10-18T01:46:16.58367429Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken"} +{"Time":"2026-10-18T01:46:16.583679907Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken","Output":"=== RUN TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken\n"} +{"Time":"2026-10-18T01:46:16.584556213Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken","Output":"--- PASS: TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.584573165Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken","Elapsed":0} +{"Time":"2026-10-18T01:46:16.584660632Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode"} +{"Time":"2026-10-18T01:46:16.584672391Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode","Output":"=== RUN TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode\n"} +{"Time":"2026-10-18T01:46:16.585083118Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode","Output":"--- PASS: TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.585100007Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_ValidToken_RedirectsWithCode","Elapsed":0} +{"Time":"2026-10-18T01:46:16.585125995Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie"} +{"Time":"2026-10-18T01:46:16.585131558Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie","Output":"=== RUN TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie\n"} +{"Time":"2026-10-18T01:46:16.585513935Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie","Output":"--- PASS: TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.585532267Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie","Elapsed":0} +{"Time":"2026-10-18T01:46:16.585549634Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse"} +{"Time":"2026-10-18T01:46:16.585555658Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse","Output":"=== RUN TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse\n"} +{"Time":"2026-10-18T01:46:16.585971984Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse","Output":"--- PASS: TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.585989649Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFANotRequired_SessionRecordsMFAVerifiedFalse","Elapsed":0} +{"Time":"2026-10-18T01:46:16.586003752Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ClientPolicyCanDisableEnrolledMFA"} +{"Time":"2026-10-18T01:46:16.586008819Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ClientPolicyCanDisableEnrolledMFA","Output":"=== RUN TestAuthorizeCallback_ClientPolicyCanDisableEnrolledMFA\n"} +{"Time":"2026-10-18T01:46:16.586375028Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ClientPolicyCanDisableEnrolledMFA","Output":"--- PASS: TestAuthorizeCallback_ClientPolicyCanDisableEnrolledMFA (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.586392539Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ClientPolicyCanDisableEnrolledMFA","Elapsed":0} +{"Time":"2026-10-18T01:46:16.586425885Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ACRStepUpOverridesClientAAL1"} +{"Time":"2026-10-18T01:46:16.586432919Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ACRStepUpOverridesClientAAL1","Output":"=== RUN TestAuthorizeCallback_ACRStepUpOverridesClientAAL1\n"} +{"Time":"2026-10-18T01:46:16.586963503Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ACRStepUpOverridesClientAAL1","Output":"--- PASS: TestAuthorizeCallback_ACRStepUpOverridesClientAAL1 (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.586991582Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ACRStepUpOverridesClientAAL1","Elapsed":0} +{"Time":"2026-10-18T01:46:16.586998562Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure"} +{"Time":"2026-10-18T01:46:16.587003368Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure","Output":"=== RUN TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure\n"} +{"Time":"2026-10-18T01:46:16.587311794Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure","Output":"--- PASS: TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.58734174Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_MFASubmission_InvalidToken_AuthFailure","Elapsed":0} +{"Time":"2026-10-18T01:46:16.58734917Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_AuthProviderFailed_AuthFailure"} +{"Time":"2026-10-18T01:46:16.587354184Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_AuthProviderFailed_AuthFailure","Output":"=== RUN TestAuthorizeCallback_AuthProviderFailed_AuthFailure\n"} +{"Time":"2026-10-18T01:46:16.587633529Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_AuthProviderFailed_AuthFailure","Output":"--- PASS: TestAuthorizeCallback_AuthProviderFailed_AuthFailure (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.587651401Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_AuthProviderFailed_AuthFailure","Elapsed":0} +{"Time":"2026-10-18T01:46:16.587692331Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_EmitsAuthSuccess"} +{"Time":"2026-10-18T01:46:16.587698787Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_EmitsAuthSuccess","Output":"=== RUN TestAuthorizeCallback_EmitsAuthSuccess\n"} +{"Time":"2026-10-18T01:46:16.588038936Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_EmitsAuthSuccess","Output":"--- PASS: TestAuthorizeCallback_EmitsAuthSuccess (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.588054959Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_EmitsAuthSuccess","Elapsed":0} +{"Time":"2026-10-18T01:46:16.588071416Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ServeHTTP_DispatchesToCallback"} +{"Time":"2026-10-18T01:46:16.588076375Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ServeHTTP_DispatchesToCallback","Output":"=== RUN TestAuthorizeHandler_ServeHTTP_DispatchesToCallback\n"} +{"Time":"2026-10-18T01:46:16.588342312Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ServeHTTP_DispatchesToCallback","Output":"--- PASS: TestAuthorizeHandler_ServeHTTP_DispatchesToCallback (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.588359283Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeHandler_ServeHTTP_DispatchesToCallback","Elapsed":0} +{"Time":"2026-10-18T01:46:16.588371844Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestFreshLoginRequirementsReachProvider"} +{"Time":"2026-10-18T01:46:16.588376695Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestFreshLoginRequirementsReachProvider","Output":"=== RUN TestFreshLoginRequirementsReachProvider\n"} +{"Time":"2026-10-18T01:46:16.588829496Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestFreshLoginRequirementsReachProvider","Output":"--- PASS: TestFreshLoginRequirementsReachProvider (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.588845475Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestFreshLoginRequirementsReachProvider","Elapsed":0} +{"Time":"2026-10-18T01:46:16.588861606Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness"} +{"Time":"2026-10-18T01:46:16.58886791Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness\n"} +{"Time":"2026-10-18T01:46:16.589071645Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_after_request"} +{"Time":"2026-10-18T01:46:16.589085622Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_after_request","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/prompt_login_after_request\n"} +{"Time":"2026-10-18T01:46:16.589407303Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_after_request","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/prompt_login_after_request (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.589421587Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_after_request","Elapsed":0} +{"Time":"2026-10-18T01:46:16.589518576Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_same_second_as_request"} +{"Time":"2026-10-18T01:46:16.589530614Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_same_second_as_request","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/prompt_login_same_second_as_request\n"} +{"Time":"2026-10-18T01:46:16.589813612Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_same_second_as_request","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/prompt_login_same_second_as_request (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.589830555Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_same_second_as_request","Elapsed":0} +{"Time":"2026-10-18T01:46:16.589839212Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_reused_session"} +{"Time":"2026-10-18T01:46:16.589844288Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_reused_session","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/prompt_login_reused_session\n"} +{"Time":"2026-10-18T01:46:16.590085815Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_reused_session","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/prompt_login_reused_session (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.59009972Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_reused_session","Elapsed":0} +{"Time":"2026-10-18T01:46:16.590113534Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_without_auth_time"} +{"Time":"2026-10-18T01:46:16.590118593Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_without_auth_time","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/prompt_login_without_auth_time\n"} +{"Time":"2026-10-18T01:46:16.590406664Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_without_auth_time","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/prompt_login_without_auth_time (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.590448828Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/prompt_login_without_auth_time","Elapsed":0} +{"Time":"2026-10-18T01:46:16.590595365Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_zero_is_prompt_login"} +{"Time":"2026-10-18T01:46:16.590608729Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_zero_is_prompt_login","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/max_age_zero_is_prompt_login\n"} +{"Time":"2026-10-18T01:46:16.590916655Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_zero_is_prompt_login","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/max_age_zero_is_prompt_login (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.590980271Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_zero_is_prompt_login","Elapsed":0} +{"Time":"2026-10-18T01:46:16.591010642Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_satisfied"} +{"Time":"2026-10-18T01:46:16.591017341Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_satisfied","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/max_age_satisfied\n"} +{"Time":"2026-10-18T01:46:16.591457556Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_satisfied","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/max_age_satisfied (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.591483652Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_satisfied","Elapsed":0} +{"Time":"2026-10-18T01:46:16.591501722Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_exceeded"} +{"Time":"2026-10-18T01:46:16.591508909Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_exceeded","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/max_age_exceeded\n"} +{"Time":"2026-10-18T01:46:16.591767208Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_exceeded","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/max_age_exceeded (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.591782159Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/max_age_exceeded","Elapsed":0} +{"Time":"2026-10-18T01:46:16.591790761Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/no_freshness_requested"} +{"Time":"2026-10-18T01:46:16.591795991Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/no_freshness_requested","Output":"=== RUN TestCallbackEnforcesUpstreamFreshness/no_freshness_requested\n"} +{"Time":"2026-10-18T01:46:16.592115012Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/no_freshness_requested","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness/no_freshness_requested (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.592133308Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness/no_freshness_requested","Elapsed":0} +{"Time":"2026-10-18T01:46:16.592144427Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness","Output":"--- PASS: TestCallbackEnforcesUpstreamFreshness (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.592229351Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCallbackEnforcesUpstreamFreshness","Elapsed":0} +{"Time":"2026-10-18T01:46:16.592245654Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseCode"} +{"Time":"2026-10-18T01:46:16.592287269Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseCode","Output":"=== RUN TestDiscoveryHandler_ResponseCode\n"} +{"Time":"2026-10-18T01:46:16.592740124Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseCode","Output":"--- PASS: TestDiscoveryHandler_ResponseCode (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.592759078Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseCode","Elapsed":0} +{"Time":"2026-10-18T01:46:16.592767897Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ContentType"} +{"Time":"2026-10-18T01:46:16.592773028Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ContentType","Output":"=== RUN TestDiscoveryHandler_ContentType\n"} +{"Time":"2026-10-18T01:46:16.593000596Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ContentType","Output":"--- PASS: TestDiscoveryHandler_ContentType (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.593016548Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ContentType","Elapsed":0} +{"Time":"2026-10-18T01:46:16.593028487Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CacheControl"} +{"Time":"2026-10-18T01:46:16.593033942Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CacheControl","Output":"=== RUN TestDiscoveryHandler_CacheControl\n"} +{"Time":"2026-10-18T01:46:16.593274081Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CacheControl","Output":"--- PASS: TestDiscoveryHandler_CacheControl (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.593289175Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CacheControl","Elapsed":0} +{"Time":"2026-10-18T01:46:16.593295668Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Issuer"} +{"Time":"2026-10-18T01:46:16.593300708Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Issuer","Output":"=== RUN TestDiscoveryHandler_Issuer\n"} +{"Time":"2026-10-18T01:46:16.593705547Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Issuer","Output":"--- PASS: TestDiscoveryHandler_Issuer (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.593720732Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Issuer","Elapsed":0} +{"Time":"2026-10-18T01:46:16.593727387Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Endpoints"} +{"Time":"2026-10-18T01:46:16.593732508Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Endpoints","Output":"=== RUN TestDiscoveryHandler_Endpoints\n"} +{"Time":"2026-10-18T01:46:16.594270153Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Endpoints","Output":"--- PASS: TestDiscoveryHandler_Endpoints (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.594292985Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Endpoints","Elapsed":0} +{"Time":"2026-10-18T01:46:16.594299973Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionAdvertisedWhenConfigured"} +{"Time":"2026-10-18T01:46:16.594306366Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionAdvertisedWhenConfigured","Output":"=== RUN TestDiscoveryHandler_EndSessionAdvertisedWhenConfigured\n"} +{"Time":"2026-10-18T01:46:16.594603521Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionAdvertisedWhenConfigured","Output":"--- PASS: TestDiscoveryHandler_EndSessionAdvertisedWhenConfigured (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.594619734Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionAdvertisedWhenConfigured","Elapsed":0} +{"Time":"2026-10-18T01:46:16.594629761Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionOmittedWhenEmpty"} +{"Time":"2026-10-18T01:46:16.594635171Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionOmittedWhenEmpty","Output":"=== RUN TestDiscoveryHandler_EndSessionOmittedWhenEmpty\n"} +{"Time":"2026-10-18T01:46:16.595130867Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionOmittedWhenEmpty","Output":"--- PASS: TestDiscoveryHandler_EndSessionOmittedWhenEmpty (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.595169524Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_EndSessionOmittedWhenEmpty","Elapsed":0} +{"Time":"2026-10-18T01:46:16.595177225Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_UserinfoOmittedWhenEmpty"} +{"Time":"2026-10-18T01:46:16.595197541Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_UserinfoOmittedWhenEmpty","Output":"=== RUN TestDiscoveryHandler_UserinfoOmittedWhenEmpty\n"} +{"Time":"2026-10-18T01:46:16.595526021Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_UserinfoOmittedWhenEmpty","Output":"--- PASS: TestDiscoveryHandler_UserinfoOmittedWhenEmpty (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.595541702Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_UserinfoOmittedWhenEmpty","Elapsed":0} +{"Time":"2026-10-18T01:46:16.595548339Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_NoRegistrationEndpoint"} +{"Time":"2026-10-18T01:46:16.595553515Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_NoRegistrationEndpoint","Output":"=== RUN TestDiscoveryHandler_NoRegistrationEndpoint\n"} +{"Time":"2026-10-18T01:46:16.59602124Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_NoRegistrationEndpoint","Output":"--- PASS: TestDiscoveryHandler_NoRegistrationEndpoint (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.596036407Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_NoRegistrationEndpoint","Elapsed":0} +{"Time":"2026-10-18T01:46:16.596042962Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseTypes"} +{"Time":"2026-10-18T01:46:16.596048508Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseTypes","Output":"=== RUN TestDiscoveryHandler_ResponseTypes\n"} +{"Time":"2026-10-18T01:46:16.596445014Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseTypes","Output":"--- PASS: TestDiscoveryHandler_ResponseTypes (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.596459893Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ResponseTypes","Elapsed":0} +{"Time":"2026-10-18T01:46:16.596466665Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_GrantTypes"} +{"Time":"2026-10-18T01:46:16.596471804Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_GrantTypes","Output":"=== RUN TestDiscoveryHandler_GrantTypes\n"} +{"Time":"2026-10-18T01:46:16.596904831Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_GrantTypes","Output":"--- PASS: TestDiscoveryHandler_GrantTypes (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.596923145Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_GrantTypes","Elapsed":0} +{"Time":"2026-10-18T01:46:16.59693163Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CodeChallengeMethod"} +{"Time":"2026-10-18T01:46:16.596936788Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CodeChallengeMethod","Output":"=== RUN TestDiscoveryHandler_CodeChallengeMethod\n"} +{"Time":"2026-10-18T01:46:16.597425415Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CodeChallengeMethod","Output":"--- PASS: TestDiscoveryHandler_CodeChallengeMethod (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.59744766Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_CodeChallengeMethod","Elapsed":0} +{"Time":"2026-10-18T01:46:16.597466425Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SigningAlg"} +{"Time":"2026-10-18T01:46:16.597484387Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SigningAlg","Output":"=== RUN TestDiscoveryHandler_SigningAlg\n"} +{"Time":"2026-10-18T01:46:16.597918415Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SigningAlg","Output":"--- PASS: TestDiscoveryHandler_SigningAlg (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.598001869Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SigningAlg","Elapsed":0} +{"Time":"2026-10-18T01:46:16.598011013Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Scopes"} +{"Time":"2026-10-18T01:46:16.598016739Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Scopes","Output":"=== RUN TestDiscoveryHandler_Scopes\n"} +{"Time":"2026-10-18T01:46:16.598423821Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Scopes","Output":"--- PASS: TestDiscoveryHandler_Scopes (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.598490396Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Scopes","Elapsed":0} +{"Time":"2026-10-18T01:46:16.598505761Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_TokenEndpointAuthMethods"} +{"Time":"2026-10-18T01:46:16.598512037Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_TokenEndpointAuthMethods","Output":"=== RUN TestDiscoveryHandler_TokenEndpointAuthMethods\n"} +{"Time":"2026-10-18T01:46:16.599008887Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_TokenEndpointAuthMethods","Output":"--- PASS: TestDiscoveryHandler_TokenEndpointAuthMethods (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.599065038Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_TokenEndpointAuthMethods","Elapsed":0} +{"Time":"2026-10-18T01:46:16.599086661Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Claims"} +{"Time":"2026-10-18T01:46:16.599094434Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Claims","Output":"=== RUN TestDiscoveryHandler_Claims\n"} +{"Time":"2026-10-18T01:46:16.599742177Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Claims","Output":"--- PASS: TestDiscoveryHandler_Claims (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.599785577Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_Claims","Elapsed":0} +{"Time":"2026-10-18T01:46:16.599795649Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryAdvertisesEveryCoreProfileClaim"} +{"Time":"2026-10-18T01:46:16.599823629Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryAdvertisesEveryCoreProfileClaim","Output":"=== RUN TestDiscoveryAdvertisesEveryCoreProfileClaim\n"} +{"Time":"2026-10-18T01:46:16.600367517Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryAdvertisesEveryCoreProfileClaim","Output":"--- PASS: TestDiscoveryAdvertisesEveryCoreProfileClaim (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.600414506Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryAdvertisesEveryCoreProfileClaim","Elapsed":0} +{"Time":"2026-10-18T01:46:16.600434614Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesIncludeConfiguredResourceScopes"} +{"Time":"2026-10-18T01:46:16.600439736Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesIncludeConfiguredResourceScopes","Output":"=== RUN TestDiscoveryScopesIncludeConfiguredResourceScopes\n"} +{"Time":"2026-10-18T01:46:16.601030042Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesIncludeConfiguredResourceScopes","Output":"--- PASS: TestDiscoveryScopesIncludeConfiguredResourceScopes (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.601068293Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesIncludeConfiguredResourceScopes","Elapsed":0} +{"Time":"2026-10-18T01:46:16.601076255Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesFallBackToBaseline"} +{"Time":"2026-10-18T01:46:16.601083018Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesFallBackToBaseline","Output":"=== RUN TestDiscoveryScopesFallBackToBaseline\n"} +{"Time":"2026-10-18T01:46:16.601490472Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesFallBackToBaseline","Output":"--- PASS: TestDiscoveryScopesFallBackToBaseline (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.601514025Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryScopesFallBackToBaseline","Elapsed":0} +{"Time":"2026-10-18T01:46:16.601522158Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SubjectTypes"} +{"Time":"2026-10-18T01:46:16.601527388Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SubjectTypes","Output":"=== RUN TestDiscoveryHandler_SubjectTypes\n"} +{"Time":"2026-10-18T01:46:16.601978909Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SubjectTypes","Output":"--- PASS: TestDiscoveryHandler_SubjectTypes (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.602019775Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_SubjectTypes","Elapsed":0} +{"Time":"2026-10-18T01:46:16.602031569Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_RequestParameterNotSupported"} +{"Time":"2026-10-18T01:46:16.602038143Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_RequestParameterNotSupported","Output":"=== RUN TestDiscoveryHandler_RequestParameterNotSupported\n"} +{"Time":"2026-10-18T01:46:16.602625165Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_RequestParameterNotSupported","Output":"--- PASS: TestDiscoveryHandler_RequestParameterNotSupported (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.602715987Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_RequestParameterNotSupported","Elapsed":0} +{"Time":"2026-10-18T01:46:16.602749892Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ClaimsParameterNotSupported"} +{"Time":"2026-10-18T01:46:16.602763291Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ClaimsParameterNotSupported","Output":"=== RUN TestDiscoveryHandler_ClaimsParameterNotSupported\n"} +{"Time":"2026-10-18T01:46:16.603322713Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ClaimsParameterNotSupported","Output":"--- PASS: TestDiscoveryHandler_ClaimsParameterNotSupported (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.603361641Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestDiscoveryHandler_ClaimsParameterNotSupported","Elapsed":0} +{"Time":"2026-10-18T01:46:16.603369747Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_EligibleClientRedirectsToAllowList"} +{"Time":"2026-10-18T01:46:16.603374436Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_EligibleClientRedirectsToAllowList","Output":"=== RUN TestHandoff_PromptCreate_EligibleClientRedirectsToAllowList\n"} +{"Time":"2026-10-18T01:46:16.604220362Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_EligibleClientRedirectsToAllowList","Output":"--- PASS: TestHandoff_PromptCreate_EligibleClientRedirectsToAllowList (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.604250967Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_EligibleClientRedirectsToAllowList","Elapsed":0} +{"Time":"2026-10-18T01:46:16.604340373Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_IneligibleClientRejected"} +{"Time":"2026-10-18T01:46:16.604349336Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_IneligibleClientRejected","Output":"=== RUN TestHandoff_PromptCreate_IneligibleClientRejected\n"} +{"Time":"2026-10-18T01:46:16.604759621Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_IneligibleClientRejected","Output":"--- PASS: TestHandoff_PromptCreate_IneligibleClientRejected (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.604790956Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_PromptCreate_IneligibleClientRejected","Elapsed":0} +{"Time":"2026-10-18T01:46:16.604798945Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReturnRestartsAuthorizeWithoutToken"} +{"Time":"2026-10-18T01:46:16.604802318Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReturnRestartsAuthorizeWithoutToken","Output":"=== RUN TestHandoff_ReturnRestartsAuthorizeWithoutToken\n"} +{"Time":"2026-10-18T01:46:16.605378953Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReturnRestartsAuthorizeWithoutToken","Output":"--- PASS: TestHandoff_ReturnRestartsAuthorizeWithoutToken (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.605396284Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReturnRestartsAuthorizeWithoutToken","Elapsed":0} +{"Time":"2026-10-18T01:46:16.605404237Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReplayRejected"} +{"Time":"2026-10-18T01:46:16.605409539Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReplayRejected","Output":"=== RUN TestHandoff_ReplayRejected\n"} +{"Time":"2026-10-18T01:46:16.606115367Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReplayRejected","Output":"--- PASS: TestHandoff_ReplayRejected (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.606152906Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_ReplayRejected","Elapsed":0} +{"Time":"2026-10-18T01:46:16.606162689Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_TamperedEnvelopeRejected"} +{"Time":"2026-10-18T01:46:16.606167513Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_TamperedEnvelopeRejected","Output":"=== RUN TestHandoff_TamperedEnvelopeRejected\n"} +{"Time":"2026-10-18T01:46:16.606617715Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_TamperedEnvelopeRejected","Output":"--- PASS: TestHandoff_TamperedEnvelopeRejected (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.606647941Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_TamperedEnvelopeRejected","Elapsed":0} +{"Time":"2026-10-18T01:46:16.606667157Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserOffersSignupWithoutToken"} +{"Time":"2026-10-18T01:46:16.606681476Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserOffersSignupWithoutToken","Output":"=== RUN TestHandoff_UnknownUserOffersSignupWithoutToken\n"} +{"Time":"2026-10-18T01:46:16.6071638Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserOffersSignupWithoutToken","Output":"--- PASS: TestHandoff_UnknownUserOffersSignupWithoutToken (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.607195513Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserOffersSignupWithoutToken","Elapsed":0} +{"Time":"2026-10-18T01:46:16.607213079Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserIneligibleHasNoSignupLink"} +{"Time":"2026-10-18T01:46:16.607217624Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserIneligibleHasNoSignupLink","Output":"=== RUN TestHandoff_UnknownUserIneligibleHasNoSignupLink\n"} +{"Time":"2026-10-18T01:46:16.607455165Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserIneligibleHasNoSignupLink","Output":"--- PASS: TestHandoff_UnknownUserIneligibleHasNoSignupLink (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.60746659Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHandoff_UnknownUserIneligibleHasNoSignupLink","Elapsed":0} +{"Time":"2026-10-18T01:46:16.607488186Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ExpiredStateRejected"} +{"Time":"2026-10-18T01:46:16.6074931Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ExpiredStateRejected","Output":"=== RUN TestAuthorizeCallback_ExpiredStateRejected\n"} +{"Time":"2026-10-18T01:46:16.607727756Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ExpiredStateRejected","Output":"--- PASS: TestAuthorizeCallback_ExpiredStateRejected (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.607741571Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ExpiredStateRejected","Elapsed":0} +{"Time":"2026-10-18T01:46:16.607748433Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ReplayAfterSuccessRejected"} +{"Time":"2026-10-18T01:46:16.607752418Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ReplayAfterSuccessRejected","Output":"=== RUN TestAuthorizeCallback_ReplayAfterSuccessRejected\n"} +{"Time":"2026-10-18T01:46:16.608016588Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ReplayAfterSuccessRejected","Output":"--- PASS: TestAuthorizeCallback_ReplayAfterSuccessRejected (0.00s)\n"} +{"Time":"2026-10-18T01:46:16.608030041Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestAuthorizeCallback_ReplayAfterSuccessRejected","Elapsed":0} +{"Time":"2026-10-18T01:46:16.608108329Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission"} +{"Time":"2026-10-18T01:46:16.608118609Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission","Output":"=== RUN TestCodeExchangeRejectsRedirectURIMismatchAndOmission\n"} +{"Time":"2026-10-18T01:46:16.60823439Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/omitted"} +{"Time":"2026-10-18T01:46:16.608245806Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/omitted","Output":"=== RUN TestCodeExchangeRejectsRedirectURIMismatchAndOmission/omitted\n"} +{"Time":"2026-10-18T01:46:16.776825696Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/omitted","Output":"--- PASS: TestCodeExchangeRejectsRedirectURIMismatchAndOmission/omitted (0.17s)\n"} +{"Time":"2026-10-18T01:46:16.776877572Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/omitted","Elapsed":0.17} +{"Time":"2026-10-18T01:46:16.776969792Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/different"} +{"Time":"2026-10-18T01:46:16.776978349Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/different","Output":"=== RUN TestCodeExchangeRejectsRedirectURIMismatchAndOmission/different\n"} +{"Time":"2026-10-18T01:46:16.960184851Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/different","Output":"--- PASS: TestCodeExchangeRejectsRedirectURIMismatchAndOmission/different (0.18s)\n"} +{"Time":"2026-10-18T01:46:16.96024138Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/different","Elapsed":0.18} +{"Time":"2026-10-18T01:46:16.960252273Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/prefix"} +{"Time":"2026-10-18T01:46:16.960258703Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/prefix","Output":"=== RUN TestCodeExchangeRejectsRedirectURIMismatchAndOmission/prefix\n"} +{"Time":"2026-10-18T01:46:16.989158254Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/prefix","Output":"--- PASS: TestCodeExchangeRejectsRedirectURIMismatchAndOmission/prefix (0.03s)\n"} +{"Time":"2026-10-18T01:46:16.989311981Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/prefix","Elapsed":0.03} +{"Time":"2026-10-18T01:46:16.989338158Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/trailing"} +{"Time":"2026-10-18T01:46:16.989348032Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/trailing","Output":"=== RUN TestCodeExchangeRejectsRedirectURIMismatchAndOmission/trailing\n"} +{"Time":"2026-10-18T01:46:17.063396748Z","Action":"output","Package":"keycape/internal/server/readiness","Output":"ok \tkeycape/internal/server/readiness\t1.231s\n"} +{"Time":"2026-10-18T01:46:17.063457624Z","Action":"pass","Package":"keycape/internal/server/readiness","Elapsed":1.231} +{"Time":"2026-10-18T01:46:17.066513091Z","Action":"start","Package":"keycape/internal/validator"} +{"Time":"2026-10-18T01:46:17.083635449Z","Action":"run","Package":"keycape/internal/validator","Test":"TestValidDNStructure_Pass"} +{"Time":"2026-10-18T01:46:17.083697731Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_Pass","Output":"=== RUN TestValidDNStructure_Pass\n"} +{"Time":"2026-10-18T01:46:17.083866407Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_Pass","Output":"--- PASS: TestValidDNStructure_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.08396456Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestValidDNStructure_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.083982134Z","Action":"run","Package":"keycape/internal/validator","Test":"TestValidDNStructure_EmptyID"} +{"Time":"2026-10-18T01:46:17.083986751Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_EmptyID","Output":"=== RUN TestValidDNStructure_EmptyID\n"} +{"Time":"2026-10-18T01:46:17.084241389Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_EmptyID","Output":"--- PASS: TestValidDNStructure_EmptyID (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.084262454Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestValidDNStructure_EmptyID","Elapsed":0} +{"Time":"2026-10-18T01:46:17.084341282Z","Action":"run","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidUsername"} +{"Time":"2026-10-18T01:46:17.084351201Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidUsername","Output":"=== RUN TestValidDNStructure_InvalidUsername\n"} +{"Time":"2026-10-18T01:46:17.084585388Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidUsername","Output":"--- PASS: TestValidDNStructure_InvalidUsername (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.084599374Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidUsername","Elapsed":0} +{"Time":"2026-10-18T01:46:17.08467328Z","Action":"run","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidGroupName"} +{"Time":"2026-10-18T01:46:17.084683244Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidGroupName","Output":"=== RUN TestValidDNStructure_InvalidGroupName\n"} +{"Time":"2026-10-18T01:46:17.084880992Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidGroupName","Output":"--- PASS: TestValidDNStructure_InvalidGroupName (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.084894094Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestValidDNStructure_InvalidGroupName","Elapsed":0} +{"Time":"2026-10-18T01:46:17.084917376Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_Pass"} +{"Time":"2026-10-18T01:46:17.084921993Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_Pass","Output":"=== RUN TestRequiredAttributesPresent_Pass\n"} +{"Time":"2026-10-18T01:46:17.085113844Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_Pass","Output":"--- PASS: TestRequiredAttributesPresent_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.085133249Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.085221618Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingUsername"} +{"Time":"2026-10-18T01:46:17.085231339Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingUsername","Output":"=== RUN TestRequiredAttributesPresent_MissingUsername\n"} +{"Time":"2026-10-18T01:46:17.085418717Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingUsername","Output":"--- PASS: TestRequiredAttributesPresent_MissingUsername (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.085433614Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingUsername","Elapsed":0} +{"Time":"2026-10-18T01:46:17.085525247Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingDisplayName"} +{"Time":"2026-10-18T01:46:17.085535283Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingDisplayName","Output":"=== RUN TestRequiredAttributesPresent_MissingDisplayName\n"} +{"Time":"2026-10-18T01:46:17.085908357Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingDisplayName","Output":"--- PASS: TestRequiredAttributesPresent_MissingDisplayName (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.085943272Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRequiredAttributesPresent_MissingDisplayName","Elapsed":0} +{"Time":"2026-10-18T01:46:17.085950206Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes"} +{"Time":"2026-10-18T01:46:17.085955148Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes","Output":"=== RUN TestRawAttributes_AcceptsLegitimateAttributes\n"} +{"Time":"2026-10-18T01:46:17.086153612Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/numeric_oid"} +{"Time":"2026-10-18T01:46:17.086183251Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/numeric_oid","Output":"=== RUN TestRawAttributes_AcceptsLegitimateAttributes/numeric_oid\n"} +{"Time":"2026-10-18T01:46:17.086440594Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/numeric_oid","Output":"--- PASS: TestRawAttributes_AcceptsLegitimateAttributes/numeric_oid (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.0864654Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/numeric_oid","Elapsed":0} +{"Time":"2026-10-18T01:46:17.08656507Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/distinct_attributes"} +{"Time":"2026-10-18T01:46:17.086578614Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/distinct_attributes","Output":"=== RUN TestRawAttributes_AcceptsLegitimateAttributes/distinct_attributes\n"} +{"Time":"2026-10-18T01:46:17.086840429Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/distinct_attributes","Output":"--- PASS: TestRawAttributes_AcceptsLegitimateAttributes/distinct_attributes (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.086891394Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/distinct_attributes","Elapsed":0} +{"Time":"2026-10-18T01:46:17.086899005Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/no_attributes"} +{"Time":"2026-10-18T01:46:17.086903766Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/no_attributes","Output":"=== RUN TestRawAttributes_AcceptsLegitimateAttributes/no_attributes\n"} +{"Time":"2026-10-18T01:46:17.087169347Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/no_attributes","Output":"--- PASS: TestRawAttributes_AcceptsLegitimateAttributes/no_attributes (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.087186624Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/no_attributes","Elapsed":0} +{"Time":"2026-10-18T01:46:17.087252685Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/simple_descriptor"} +{"Time":"2026-10-18T01:46:17.087261287Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/simple_descriptor","Output":"=== RUN TestRawAttributes_AcceptsLegitimateAttributes/simple_descriptor\n"} +{"Time":"2026-10-18T01:46:17.087477178Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/simple_descriptor","Output":"--- PASS: TestRawAttributes_AcceptsLegitimateAttributes/simple_descriptor (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.087556999Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/simple_descriptor","Elapsed":0} +{"Time":"2026-10-18T01:46:17.087649353Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/hyphenated_and_digits"} +{"Time":"2026-10-18T01:46:17.087657911Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/hyphenated_and_digits","Output":"=== RUN TestRawAttributes_AcceptsLegitimateAttributes/hyphenated_and_digits\n"} +{"Time":"2026-10-18T01:46:17.087841624Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/trailing","Output":"--- PASS: TestCodeExchangeRejectsRedirectURIMismatchAndOmission/trailing (0.10s)\n"} +{"Time":"2026-10-18T01:46:17.087993452Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission/trailing","Elapsed":0.1} +{"Time":"2026-10-18T01:46:17.088013272Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission","Output":"--- PASS: TestCodeExchangeRejectsRedirectURIMismatchAndOmission (0.48s)\n"} +{"Time":"2026-10-18T01:46:17.088014026Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/hyphenated_and_digits","Output":"--- PASS: TestRawAttributes_AcceptsLegitimateAttributes/hyphenated_and_digits (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.088056552Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes/hyphenated_and_digits","Elapsed":0} +{"Time":"2026-10-18T01:46:17.088081448Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes","Output":"--- PASS: TestRawAttributes_AcceptsLegitimateAttributes (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.088105348Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsRedirectURIMismatchAndOmission","Elapsed":0.48} +{"Time":"2026-10-18T01:46:17.08819525Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsServiceOnlyClient"} +{"Time":"2026-10-18T01:46:17.08822993Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsServiceOnlyClient","Output":"=== RUN TestCodeExchangeRejectsServiceOnlyClient\n"} +{"Time":"2026-10-18T01:46:17.08849341Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_AcceptsLegitimateAttributes","Elapsed":0} +{"Time":"2026-10-18T01:46:17.08851259Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors"} +{"Time":"2026-10-18T01:46:17.088517459Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors\n"} +{"Time":"2026-10-18T01:46:17.088717696Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/underscore"} +{"Time":"2026-10-18T01:46:17.088729013Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/underscore","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/underscore\n"} +{"Time":"2026-10-18T01:46:17.088922766Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/underscore","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/underscore (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.088942685Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/underscore","Elapsed":0} +{"Time":"2026-10-18T01:46:17.089025761Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/transfer_option"} +{"Time":"2026-10-18T01:46:17.08903559Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/transfer_option","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/transfer_option\n"} +{"Time":"2026-10-18T01:46:17.089390356Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/transfer_option","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/transfer_option (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.089411177Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/transfer_option","Elapsed":0} +{"Time":"2026-10-18T01:46:17.089490718Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/single_arc_oid"} +{"Time":"2026-10-18T01:46:17.089501322Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/single_arc_oid","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/single_arc_oid\n"} +{"Time":"2026-10-18T01:46:17.08976512Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/single_arc_oid","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/single_arc_oid (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.089809043Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/single_arc_oid","Elapsed":0} +{"Time":"2026-10-18T01:46:17.089839152Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/blank"} +{"Time":"2026-10-18T01:46:17.089846718Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/blank","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/blank\n"} +{"Time":"2026-10-18T01:46:17.090085559Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/blank","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/blank (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.090102646Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/blank","Elapsed":0} +{"Time":"2026-10-18T01:46:17.090123828Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/whitespace_only"} +{"Time":"2026-10-18T01:46:17.090128309Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/whitespace_only","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/whitespace_only\n"} +{"Time":"2026-10-18T01:46:17.090329602Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/whitespace_only","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/whitespace_only (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.090452329Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/whitespace_only","Elapsed":0} +{"Time":"2026-10-18T01:46:17.090470328Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_hyphen"} +{"Time":"2026-10-18T01:46:17.090475666Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_hyphen","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/leading_hyphen\n"} +{"Time":"2026-10-18T01:46:17.090731945Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_hyphen","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/leading_hyphen (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.090877393Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_hyphen","Elapsed":0} +{"Time":"2026-10-18T01:46:17.090891911Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/space_inside"} +{"Time":"2026-10-18T01:46:17.090897583Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/space_inside","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/space_inside\n"} +{"Time":"2026-10-18T01:46:17.091096338Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/space_inside","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/space_inside (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.091114382Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/space_inside","Elapsed":0} +{"Time":"2026-10-18T01:46:17.091121929Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/empty_oid_arc"} +{"Time":"2026-10-18T01:46:17.091125888Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/empty_oid_arc","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/empty_oid_arc\n"} +{"Time":"2026-10-18T01:46:17.091386288Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/empty_oid_arc","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/empty_oid_arc (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.091399742Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/empty_oid_arc","Elapsed":0} +{"Time":"2026-10-18T01:46:17.091478498Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_zero_oid"} +{"Time":"2026-10-18T01:46:17.091488417Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_zero_oid","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/leading_zero_oid\n"} +{"Time":"2026-10-18T01:46:17.09167144Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_zero_oid","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/leading_zero_oid (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.091696995Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_zero_oid","Elapsed":0} +{"Time":"2026-10-18T01:46:17.091777077Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/non_numeric_oid_arc"} +{"Time":"2026-10-18T01:46:17.091787285Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/non_numeric_oid_arc","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/non_numeric_oid_arc\n"} +{"Time":"2026-10-18T01:46:17.091971065Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/non_numeric_oid_arc","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/non_numeric_oid_arc (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.091984456Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/non_numeric_oid_arc","Elapsed":0} +{"Time":"2026-10-18T01:46:17.092063059Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_digit"} +{"Time":"2026-10-18T01:46:17.092073005Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_digit","Output":"=== RUN TestRawAttributes_RejectsMalformedDescriptors/leading_digit\n"} +{"Time":"2026-10-18T01:46:17.092300691Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_digit","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors/leading_digit (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.092389502Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors/leading_digit","Elapsed":0} +{"Time":"2026-10-18T01:46:17.092502362Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors","Output":"--- PASS: TestRawAttributes_RejectsMalformedDescriptors (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.092524553Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsMalformedDescriptors","Elapsed":0} +{"Time":"2026-10-18T01:46:17.09259496Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings"} +{"Time":"2026-10-18T01:46:17.092605393Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings\n"} +{"Time":"2026-10-18T01:46:17.092731238Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/uid"} +{"Time":"2026-10-18T01:46:17.092741355Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/uid","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings/uid\n"} +{"Time":"2026-10-18T01:46:17.092897741Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/uid","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings/uid (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.09290959Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/uid","Elapsed":0} +{"Time":"2026-10-18T01:46:17.092916389Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cn"} +{"Time":"2026-10-18T01:46:17.092920392Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cn","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings/cn\n"} +{"Time":"2026-10-18T01:46:17.093114582Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cn","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings/cn (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.093152319Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cn","Elapsed":0} +{"Time":"2026-10-18T01:46:17.093159605Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/mail"} +{"Time":"2026-10-18T01:46:17.09316387Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/mail","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings/mail\n"} +{"Time":"2026-10-18T01:46:17.093390516Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/mail","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings/mail (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.093407531Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/mail","Elapsed":0} +{"Time":"2026-10-18T01:46:17.093481076Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/UID"} +{"Time":"2026-10-18T01:46:17.093491184Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/UID","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings/UID\n"} +{"Time":"2026-10-18T01:46:17.093664117Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/UID","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings/UID (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.093677147Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/UID","Elapsed":0} +{"Time":"2026-10-18T01:46:17.093760043Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/Mail"} +{"Time":"2026-10-18T01:46:17.093769917Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/Mail","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings/Mail\n"} +{"Time":"2026-10-18T01:46:17.093941677Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/Mail","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings/Mail (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.093954923Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/Mail","Elapsed":0} +{"Time":"2026-10-18T01:46:17.094035889Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cN"} +{"Time":"2026-10-18T01:46:17.094045664Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cN","Output":"=== RUN TestRawAttributes_RejectsCanonicalMappings/cN\n"} +{"Time":"2026-10-18T01:46:17.094220427Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cN","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings/cN (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.094233232Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings/cN","Elapsed":0} +{"Time":"2026-10-18T01:46:17.094403045Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings","Output":"--- PASS: TestRawAttributes_RejectsCanonicalMappings (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.094417959Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCanonicalMappings","Elapsed":0} +{"Time":"2026-10-18T01:46:17.09449365Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCaseOnlyDuplicates"} +{"Time":"2026-10-18T01:46:17.094503578Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCaseOnlyDuplicates","Output":"=== RUN TestRawAttributes_RejectsCaseOnlyDuplicates\n"} +{"Time":"2026-10-18T01:46:17.094737143Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCaseOnlyDuplicates","Output":"--- PASS: TestRawAttributes_RejectsCaseOnlyDuplicates (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.094888533Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_RejectsCaseOnlyDuplicates","Elapsed":0} +{"Time":"2026-10-18T01:46:17.094901617Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_MessageNamesUserAndAttribute"} +{"Time":"2026-10-18T01:46:17.094906054Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_MessageNamesUserAndAttribute","Output":"=== RUN TestRawAttributes_MessageNamesUserAndAttribute\n"} +{"Time":"2026-10-18T01:46:17.095057201Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_MessageNamesUserAndAttribute","Output":"--- PASS: TestRawAttributes_MessageNamesUserAndAttribute (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.095068851Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_MessageNamesUserAndAttribute","Elapsed":0} +{"Time":"2026-10-18T01:46:17.095075605Z","Action":"run","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_Pass"} +{"Time":"2026-10-18T01:46:17.095079997Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_Pass","Output":"=== RUN TestValidGroupMemberships_Pass\n"} +{"Time":"2026-10-18T01:46:17.095262091Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_Pass","Output":"--- PASS: TestValidGroupMemberships_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.095276284Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.095282993Z","Action":"run","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_BlankMember"} +{"Time":"2026-10-18T01:46:17.095286915Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_BlankMember","Output":"=== RUN TestValidGroupMemberships_BlankMember\n"} +{"Time":"2026-10-18T01:46:17.09546882Z","Action":"output","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_BlankMember","Output":"--- PASS: TestValidGroupMemberships_BlankMember (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.095480254Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestValidGroupMemberships_BlankMember","Elapsed":0} +{"Time":"2026-10-18T01:46:17.095490976Z","Action":"run","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_Pass"} +{"Time":"2026-10-18T01:46:17.095494871Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_Pass","Output":"=== RUN TestReferencedUsersExist_Pass\n"} +{"Time":"2026-10-18T01:46:17.095664432Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_Pass","Output":"--- PASS: TestReferencedUsersExist_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.095675739Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.095682141Z","Action":"run","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_UnknownUser"} +{"Time":"2026-10-18T01:46:17.095686073Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_UnknownUser","Output":"=== RUN TestReferencedUsersExist_UnknownUser\n"} +{"Time":"2026-10-18T01:46:17.095876499Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_UnknownUser","Output":"--- PASS: TestReferencedUsersExist_UnknownUser (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.095887861Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestReferencedUsersExist_UnknownUser","Elapsed":0} +{"Time":"2026-10-18T01:46:17.095899313Z","Action":"run","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_Pass"} +{"Time":"2026-10-18T01:46:17.095918502Z","Action":"output","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_Pass","Output":"=== RUN TestNoCyclicGroups_Pass\n"} +{"Time":"2026-10-18T01:46:17.096074193Z","Action":"output","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_Pass","Output":"--- PASS: TestNoCyclicGroups_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.096085708Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.096092264Z","Action":"run","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_GroupInMembers"} +{"Time":"2026-10-18T01:46:17.096096177Z","Action":"output","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_GroupInMembers","Output":"=== RUN TestNoCyclicGroups_GroupInMembers\n"} +{"Time":"2026-10-18T01:46:17.096278993Z","Action":"output","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_GroupInMembers","Output":"--- PASS: TestNoCyclicGroups_GroupInMembers (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.096290544Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestNoCyclicGroups_GroupInMembers","Elapsed":0} +{"Time":"2026-10-18T01:46:17.09630003Z","Action":"run","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Pass"} +{"Time":"2026-10-18T01:46:17.096304079Z","Action":"output","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Pass","Output":"=== RUN TestUsernamesUnique_Pass\n"} +{"Time":"2026-10-18T01:46:17.096488283Z","Action":"output","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Pass","Output":"--- PASS: TestUsernamesUnique_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.096499519Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.096506093Z","Action":"run","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Duplicate"} +{"Time":"2026-10-18T01:46:17.096510042Z","Action":"output","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Duplicate","Output":"=== RUN TestUsernamesUnique_Duplicate\n"} +{"Time":"2026-10-18T01:46:17.096689089Z","Action":"output","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Duplicate","Output":"--- PASS: TestUsernamesUnique_Duplicate (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.096700297Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestUsernamesUnique_Duplicate","Elapsed":0} +{"Time":"2026-10-18T01:46:17.096785041Z","Action":"run","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_Pass"} +{"Time":"2026-10-18T01:46:17.096794753Z","Action":"output","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_Pass","Output":"=== RUN TestEmailFormatValid_Pass\n"} +{"Time":"2026-10-18T01:46:17.096905683Z","Action":"output","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_Pass","Output":"--- PASS: TestEmailFormatValid_Pass (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.096938352Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_Pass","Elapsed":0} +{"Time":"2026-10-18T01:46:17.097003935Z","Action":"run","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_InvalidEmail"} +{"Time":"2026-10-18T01:46:17.097013655Z","Action":"output","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_InvalidEmail","Output":"=== RUN TestEmailFormatValid_InvalidEmail\n"} +{"Time":"2026-10-18T01:46:17.097174289Z","Action":"output","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_InvalidEmail","Output":"--- PASS: TestEmailFormatValid_InvalidEmail (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.097185587Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_InvalidEmail","Elapsed":0} +{"Time":"2026-10-18T01:46:17.097196119Z","Action":"run","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_EmptyEmailSkipped"} +{"Time":"2026-10-18T01:46:17.097200105Z","Action":"output","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_EmptyEmailSkipped","Output":"=== RUN TestEmailFormatValid_EmptyEmailSkipped\n"} +{"Time":"2026-10-18T01:46:17.097383173Z","Action":"output","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_EmptyEmailSkipped","Output":"--- PASS: TestEmailFormatValid_EmptyEmailSkipped (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.097395193Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestEmailFormatValid_EmptyEmailSkipped","Elapsed":0} +{"Time":"2026-10-18T01:46:17.097401812Z","Action":"run","Package":"keycape/internal/validator","Test":"TestReportPassed_AllGood"} +{"Time":"2026-10-18T01:46:17.097405752Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReportPassed_AllGood","Output":"=== RUN TestReportPassed_AllGood\n"} +{"Time":"2026-10-18T01:46:17.097620246Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReportPassed_AllGood","Output":"--- PASS: TestReportPassed_AllGood (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.097631743Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestReportPassed_AllGood","Elapsed":0} +{"Time":"2026-10-18T01:46:17.097638127Z","Action":"run","Package":"keycape/internal/validator","Test":"TestReportFailed_OneRuleFails"} +{"Time":"2026-10-18T01:46:17.09764214Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReportFailed_OneRuleFails","Output":"=== RUN TestReportFailed_OneRuleFails\n"} +{"Time":"2026-10-18T01:46:17.097816037Z","Action":"output","Package":"keycape/internal/validator","Test":"TestReportFailed_OneRuleFails","Output":"--- PASS: TestReportFailed_OneRuleFails (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.097827417Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestReportFailed_OneRuleFails","Elapsed":0} +{"Time":"2026-10-18T01:46:17.097916967Z","Action":"run","Package":"keycape/internal/validator","Test":"TestModeRecordedInReport"} +{"Time":"2026-10-18T01:46:17.097928158Z","Action":"output","Package":"keycape/internal/validator","Test":"TestModeRecordedInReport","Output":"=== RUN TestModeRecordedInReport\n"} +{"Time":"2026-10-18T01:46:17.098075437Z","Action":"output","Package":"keycape/internal/validator","Test":"TestModeRecordedInReport","Output":"--- PASS: TestModeRecordedInReport (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.098088548Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestModeRecordedInReport","Elapsed":0} +{"Time":"2026-10-18T01:46:17.098101803Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_IgnoresToolingAnnotations"} +{"Time":"2026-10-18T01:46:17.098106375Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_IgnoresToolingAnnotations","Output":"=== RUN TestRawAttributes_IgnoresToolingAnnotations\n"} +{"Time":"2026-10-18T01:46:17.098307928Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_IgnoresToolingAnnotations","Output":"--- PASS: TestRawAttributes_IgnoresToolingAnnotations (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.098325512Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_IgnoresToolingAnnotations","Elapsed":0} +{"Time":"2026-10-18T01:46:17.098331508Z","Action":"run","Package":"keycape/internal/validator","Test":"TestRawAttributes_ExemptionDoesNotWeakenTheRule"} +{"Time":"2026-10-18T01:46:17.09833893Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_ExemptionDoesNotWeakenTheRule","Output":"=== RUN TestRawAttributes_ExemptionDoesNotWeakenTheRule\n"} +{"Time":"2026-10-18T01:46:17.098479499Z","Action":"output","Package":"keycape/internal/validator","Test":"TestRawAttributes_ExemptionDoesNotWeakenTheRule","Output":"--- PASS: TestRawAttributes_ExemptionDoesNotWeakenTheRule (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.098490978Z","Action":"pass","Package":"keycape/internal/validator","Test":"TestRawAttributes_ExemptionDoesNotWeakenTheRule","Elapsed":0} +{"Time":"2026-10-18T01:46:17.098590502Z","Action":"output","Package":"keycape/internal/validator","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:17.120293803Z","Action":"output","Package":"keycape/internal/server/telemetry","Output":"ok \tkeycape/internal/server/telemetry\t1.027s\n"} +{"Time":"2026-10-18T01:46:17.120353209Z","Action":"pass","Package":"keycape/internal/server/telemetry","Elapsed":1.027} +{"Time":"2026-10-18T01:46:17.121999849Z","Action":"start","Package":"keycape/tests/conformance"} +{"Time":"2026-10-18T01:46:17.141607588Z","Action":"run","Package":"keycape/tests/conformance","Test":"TestIssuerAdvertisesItself"} +{"Time":"2026-10-18T01:46:17.141651926Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestIssuerAdvertisesItself","Output":"=== RUN TestIssuerAdvertisesItself\n"} +{"Time":"2026-10-18T01:46:17.141989556Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestIssuerAdvertisesItself","Output":" external_test.go:101: KEYCAPE_TEST_ISSUER is not set; this suite targets a running issuer\n"} +{"Time":"2026-10-18T01:46:17.142045237Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestIssuerAdvertisesItself","Output":"--- SKIP: TestIssuerAdvertisesItself (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.142069196Z","Action":"skip","Package":"keycape/tests/conformance","Test":"TestIssuerAdvertisesItself","Elapsed":0} +{"Time":"2026-10-18T01:46:17.142090892Z","Action":"run","Package":"keycape/tests/conformance","Test":"TestProfileAuthorizationSurface"} +{"Time":"2026-10-18T01:46:17.142110198Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestProfileAuthorizationSurface","Output":"=== RUN TestProfileAuthorizationSurface\n"} +{"Time":"2026-10-18T01:46:17.142412348Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestProfileAuthorizationSurface","Output":" external_test.go:111: KEYCAPE_TEST_ISSUER is not set; this suite targets a running issuer\n"} +{"Time":"2026-10-18T01:46:17.142452718Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestProfileAuthorizationSurface","Output":"--- SKIP: TestProfileAuthorizationSurface (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.142473457Z","Action":"skip","Package":"keycape/tests/conformance","Test":"TestProfileAuthorizationSurface","Elapsed":0} +{"Time":"2026-10-18T01:46:17.142485595Z","Action":"run","Package":"keycape/tests/conformance","Test":"TestPublishedKeysAreUsable"} +{"Time":"2026-10-18T01:46:17.142495108Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestPublishedKeysAreUsable","Output":"=== RUN TestPublishedKeysAreUsable\n"} +{"Time":"2026-10-18T01:46:17.14272391Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestPublishedKeysAreUsable","Output":" external_test.go:145: KEYCAPE_TEST_ISSUER is not set; this suite targets a running issuer\n"} +{"Time":"2026-10-18T01:46:17.142759312Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestPublishedKeysAreUsable","Output":"--- SKIP: TestPublishedKeysAreUsable (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.142770268Z","Action":"skip","Package":"keycape/tests/conformance","Test":"TestPublishedKeysAreUsable","Elapsed":0} +{"Time":"2026-10-18T01:46:17.142975998Z","Action":"run","Package":"keycape/tests/conformance","Test":"TestExcludedGrantsAreNotAdvertised"} +{"Time":"2026-10-18T01:46:17.143000678Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestExcludedGrantsAreNotAdvertised","Output":"=== RUN TestExcludedGrantsAreNotAdvertised\n"} +{"Time":"2026-10-18T01:46:17.143012942Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestExcludedGrantsAreNotAdvertised","Output":" external_test.go:162: KEYCAPE_TEST_ISSUER is not set; this suite targets a running issuer\n"} +{"Time":"2026-10-18T01:46:17.143033438Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestExcludedGrantsAreNotAdvertised","Output":"--- SKIP: TestExcludedGrantsAreNotAdvertised (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.143187437Z","Action":"skip","Package":"keycape/tests/conformance","Test":"TestExcludedGrantsAreNotAdvertised","Elapsed":0} +{"Time":"2026-10-18T01:46:17.143202162Z","Action":"run","Package":"keycape/tests/conformance","Test":"TestServiceTokenVerifiesAgainstPublishedKeys"} +{"Time":"2026-10-18T01:46:17.143219953Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestServiceTokenVerifiesAgainstPublishedKeys","Output":"=== RUN TestServiceTokenVerifiesAgainstPublishedKeys\n"} +{"Time":"2026-10-18T01:46:17.143500702Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestServiceTokenVerifiesAgainstPublishedKeys","Output":" external_test.go:177: KEYCAPE_TEST_CLIENT_ID/SECRET not set; skipping token exchange\n"} +{"Time":"2026-10-18T01:46:17.143531433Z","Action":"output","Package":"keycape/tests/conformance","Test":"TestServiceTokenVerifiesAgainstPublishedKeys","Output":"--- SKIP: TestServiceTokenVerifiesAgainstPublishedKeys (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.143696016Z","Action":"skip","Package":"keycape/tests/conformance","Test":"TestServiceTokenVerifiesAgainstPublishedKeys","Elapsed":0} +{"Time":"2026-10-18T01:46:17.143712049Z","Action":"output","Package":"keycape/tests/conformance","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:17.266409337Z","Action":"output","Package":"keycape/internal/server/policy","Output":"ok \tkeycape/internal/server/policy\t1.434s\n"} +{"Time":"2026-10-18T01:46:17.266457831Z","Action":"pass","Package":"keycape/internal/server/policy","Elapsed":1.434} +{"Time":"2026-10-18T01:46:17.26809246Z","Action":"start","Package":"keycape/tests/migration"} +{"Time":"2026-10-18T01:46:17.284004623Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBRealmPreservesClients"} +{"Time":"2026-10-18T01:46:17.284055192Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBRealmPreservesClients","Output":"=== RUN TestScenarioBRealmPreservesClients\n"} +{"Time":"2026-10-18T01:46:17.284359507Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBRealmPreservesClients","Output":"--- PASS: TestScenarioBRealmPreservesClients (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.284404846Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBRealmPreservesClients","Elapsed":0} +{"Time":"2026-10-18T01:46:17.284587622Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBNoImplicitFlow"} +{"Time":"2026-10-18T01:46:17.284606639Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBNoImplicitFlow","Output":"=== RUN TestScenarioBNoImplicitFlow\n"} +{"Time":"2026-10-18T01:46:17.284720126Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBNoImplicitFlow","Output":"--- PASS: TestScenarioBNoImplicitFlow (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.284804636Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBNoImplicitFlow","Elapsed":0} +{"Time":"2026-10-18T01:46:17.284818694Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBNoIdentityBrokers"} +{"Time":"2026-10-18T01:46:17.284824158Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBNoIdentityBrokers","Output":"=== RUN TestScenarioBNoIdentityBrokers\n"} +{"Time":"2026-10-18T01:46:17.285096485Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBNoIdentityBrokers","Output":"--- PASS: TestScenarioBNoIdentityBrokers (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.285120011Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBNoIdentityBrokers","Elapsed":0} +{"Time":"2026-10-18T01:46:17.285147Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBUsersPreserved"} +{"Time":"2026-10-18T01:46:17.285154133Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBUsersPreserved","Output":"=== RUN TestScenarioBUsersPreserved\n"} +{"Time":"2026-10-18T01:46:17.285289671Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBUsersPreserved","Output":"--- PASS: TestScenarioBUsersPreserved (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.285302151Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBUsersPreserved","Elapsed":0} +{"Time":"2026-10-18T01:46:17.285323972Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBGroupsPreserved"} +{"Time":"2026-10-18T01:46:17.285328319Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBGroupsPreserved","Output":"=== RUN TestScenarioBGroupsPreserved\n"} +{"Time":"2026-10-18T01:46:17.285536139Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBGroupsPreserved","Output":"--- PASS: TestScenarioBGroupsPreserved (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.2855498Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBGroupsPreserved","Elapsed":0} +{"Time":"2026-10-18T01:46:17.285625085Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBSigningAlgorithmPreserved"} +{"Time":"2026-10-18T01:46:17.285634644Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBSigningAlgorithmPreserved","Output":"=== RUN TestScenarioBSigningAlgorithmPreserved\n"} +{"Time":"2026-10-18T01:46:17.285805665Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBSigningAlgorithmPreserved","Output":"--- PASS: TestScenarioBSigningAlgorithmPreserved (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.285817432Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBSigningAlgorithmPreserved","Elapsed":0} +{"Time":"2026-10-18T01:46:17.285844051Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBValidationReport"} +{"Time":"2026-10-18T01:46:17.28584856Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBValidationReport","Output":"=== RUN TestScenarioBValidationReport\n"} +{"Time":"2026-10-18T01:46:17.28606896Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBValidationReport","Output":"--- PASS: TestScenarioBValidationReport (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.28608116Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBValidationReport","Elapsed":0} +{"Time":"2026-10-18T01:46:17.286154705Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioBPublicClientMapping"} +{"Time":"2026-10-18T01:46:17.286164389Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBPublicClientMapping","Output":"=== RUN TestScenarioBPublicClientMapping\n"} +{"Time":"2026-10-18T01:46:17.286399983Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioBPublicClientMapping","Output":"--- PASS: TestScenarioBPublicClientMapping (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.286418567Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioBPublicClientMapping","Elapsed":0} +{"Time":"2026-10-18T01:46:17.286451598Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCLDIFRoundTrip"} +{"Time":"2026-10-18T01:46:17.286460732Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCLDIFRoundTrip","Output":"=== RUN TestScenarioCLDIFRoundTrip\n"} +{"Time":"2026-10-18T01:46:17.286650617Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCLDIFRoundTrip","Output":"--- PASS: TestScenarioCLDIFRoundTrip (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.286675279Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCLDIFRoundTrip","Elapsed":0} +{"Time":"2026-10-18T01:46:17.286739338Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCTargetDifferences"} +{"Time":"2026-10-18T01:46:17.286760402Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCTargetDifferences","Output":"=== RUN TestScenarioCTargetDifferences\n"} +{"Time":"2026-10-18T01:46:17.28709166Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCTargetDifferences","Output":"--- PASS: TestScenarioCTargetDifferences (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.287104302Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCTargetDifferences","Elapsed":0} +{"Time":"2026-10-18T01:46:17.28713937Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCMFANotMigrated"} +{"Time":"2026-10-18T01:46:17.287158099Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCMFANotMigrated","Output":"=== RUN TestScenarioCMFANotMigrated\n"} +{"Time":"2026-10-18T01:46:17.287396389Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCMFANotMigrated","Output":"--- PASS: TestScenarioCMFANotMigrated (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.287414035Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCMFANotMigrated","Elapsed":0} +{"Time":"2026-10-18T01:46:17.287519357Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCStructuralEntries"} +{"Time":"2026-10-18T01:46:17.287529005Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCStructuralEntries","Output":"=== RUN TestScenarioCStructuralEntries\n"} +{"Time":"2026-10-18T01:46:17.28772816Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCStructuralEntries","Output":"--- PASS: TestScenarioCStructuralEntries (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.287741271Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCStructuralEntries","Elapsed":0} +{"Time":"2026-10-18T01:46:17.287747854Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCUserPreservation"} +{"Time":"2026-10-18T01:46:17.287751731Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCUserPreservation","Output":"=== RUN TestScenarioCUserPreservation\n"} +{"Time":"2026-10-18T01:46:17.287965548Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCUserPreservation","Output":"--- PASS: TestScenarioCUserPreservation (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.287978705Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCUserPreservation","Elapsed":0} +{"Time":"2026-10-18T01:46:17.287985945Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCGroupMembersPreserved"} +{"Time":"2026-10-18T01:46:17.287990039Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCGroupMembersPreserved","Output":"=== RUN TestScenarioCGroupMembersPreserved\n"} +{"Time":"2026-10-18T01:46:17.288234993Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCGroupMembersPreserved","Output":"--- PASS: TestScenarioCGroupMembersPreserved (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.288248501Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCGroupMembersPreserved","Elapsed":0} +{"Time":"2026-10-18T01:46:17.288332956Z","Action":"run","Package":"keycape/tests/migration","Test":"TestScenarioCOrthogonality"} +{"Time":"2026-10-18T01:46:17.288343299Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCOrthogonality","Output":"=== RUN TestScenarioCOrthogonality\n"} +{"Time":"2026-10-18T01:46:17.288570056Z","Action":"output","Package":"keycape/tests/migration","Test":"TestScenarioCOrthogonality","Output":"--- PASS: TestScenarioCOrthogonality (0.00s)\n"} +{"Time":"2026-10-18T01:46:17.288581688Z","Action":"pass","Package":"keycape/tests/migration","Test":"TestScenarioCOrthogonality","Elapsed":0} +{"Time":"2026-10-18T01:46:17.288666874Z","Action":"output","Package":"keycape/tests/migration","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:17.291352112Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsServiceOnlyClient","Output":"--- PASS: TestCodeExchangeRejectsServiceOnlyClient (0.20s)\n"} +{"Time":"2026-10-18T01:46:17.291402743Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeRejectsServiceOnlyClient","Elapsed":0.2} +{"Time":"2026-10-18T01:46:17.291416323Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeAllowsImplicitAuthorizationCodeClient"} +{"Time":"2026-10-18T01:46:17.291423357Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeAllowsImplicitAuthorizationCodeClient","Output":"=== RUN TestCodeExchangeAllowsImplicitAuthorizationCodeClient\n"} +{"Time":"2026-10-18T01:46:17.655252036Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeAllowsImplicitAuthorizationCodeClient","Output":"--- PASS: TestCodeExchangeAllowsImplicitAuthorizationCodeClient (0.36s)\n"} +{"Time":"2026-10-18T01:46:17.655319827Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestCodeExchangeAllowsImplicitAuthorizationCodeClient","Elapsed":0.36} +{"Time":"2026-10-18T01:46:17.655340594Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret"} +{"Time":"2026-10-18T01:46:17.655348971Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret","Output":"=== RUN TestConfidentialCodeClientRequiresItsSecret\n"} +{"Time":"2026-10-18T01:46:17.655363227Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/no_credentials"} +{"Time":"2026-10-18T01:46:17.655371081Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/no_credentials","Output":"=== RUN TestConfidentialCodeClientRequiresItsSecret/no_credentials\n"} +{"Time":"2026-10-18T01:46:17.711103992Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/no_credentials","Output":"--- PASS: TestConfidentialCodeClientRequiresItsSecret/no_credentials (0.06s)\n"} +{"Time":"2026-10-18T01:46:17.711212451Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/no_credentials","Elapsed":0.06} +{"Time":"2026-10-18T01:46:17.711289206Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/wrong_secret"} +{"Time":"2026-10-18T01:46:17.711299288Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/wrong_secret","Output":"=== RUN TestConfidentialCodeClientRequiresItsSecret/wrong_secret\n"} +{"Time":"2026-10-18T01:46:17.781710378Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/wrong_secret","Output":"--- PASS: TestConfidentialCodeClientRequiresItsSecret/wrong_secret (0.07s)\n"} +{"Time":"2026-10-18T01:46:17.781778377Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/wrong_secret","Elapsed":0.07} +{"Time":"2026-10-18T01:46:17.78179496Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/other_client_identity"} +{"Time":"2026-10-18T01:46:17.781809117Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/other_client_identity","Output":"=== RUN TestConfidentialCodeClientRequiresItsSecret/other_client_identity\n"} +{"Time":"2026-10-18T01:46:18.10019865Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/other_client_identity","Output":"--- PASS: TestConfidentialCodeClientRequiresItsSecret/other_client_identity (0.32s)\n"} +{"Time":"2026-10-18T01:46:18.100265441Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/other_client_identity","Elapsed":0.32} +{"Time":"2026-10-18T01:46:18.10028637Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/correct_secret"} +{"Time":"2026-10-18T01:46:18.100295748Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/correct_secret","Output":"=== RUN TestConfidentialCodeClientRequiresItsSecret/correct_secret\n"} +{"Time":"2026-10-18T01:46:18.102948355Z","Action":"output","Package":"keycape/internal/validator","Output":"ok \tkeycape/internal/validator\t1.036s\n"} +{"Time":"2026-10-18T01:46:18.103004081Z","Action":"pass","Package":"keycape/internal/validator","Elapsed":1.037} +{"Time":"2026-10-18T01:46:18.104738513Z","Action":"start","Package":"keycape/tests/negative"} +{"Time":"2026-10-18T01:46:18.120887385Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_DynamicClientRegistration"} +{"Time":"2026-10-18T01:46:18.120937421Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_DynamicClientRegistration","Output":"=== RUN TestNegative_DynamicClientRegistration\n"} +{"Time":"2026-10-18T01:46:18.125772188Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_DynamicClientRegistration","Output":"--- PASS: TestNegative_DynamicClientRegistration (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.125827139Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_DynamicClientRegistration","Elapsed":0} +{"Time":"2026-10-18T01:46:18.12583878Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_ImplicitFlow"} +{"Time":"2026-10-18T01:46:18.12584346Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_ImplicitFlow","Output":"=== RUN TestNegative_ImplicitFlow\n"} +{"Time":"2026-10-18T01:46:18.129185621Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_ImplicitFlow","Output":"--- PASS: TestNegative_ImplicitFlow (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.129239472Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_ImplicitFlow","Elapsed":0} +{"Time":"2026-10-18T01:46:18.129249571Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_WildcardRedirectURI"} +{"Time":"2026-10-18T01:46:18.129261077Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_WildcardRedirectURI","Output":"=== RUN TestNegative_WildcardRedirectURI\n"} +{"Time":"2026-10-18T01:46:18.131984002Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_WildcardRedirectURI","Output":"--- PASS: TestNegative_WildcardRedirectURI (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.132032461Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_WildcardRedirectURI","Elapsed":0} +{"Time":"2026-10-18T01:46:18.132039458Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_IdentityBroker"} +{"Time":"2026-10-18T01:46:18.132045157Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_IdentityBroker","Output":"=== RUN TestNegative_IdentityBroker\n"} +{"Time":"2026-10-18T01:46:18.134347401Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_IdentityBroker","Output":"--- PASS: TestNegative_IdentityBroker (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.134398853Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_IdentityBroker","Elapsed":0} +{"Time":"2026-10-18T01:46:18.134405833Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_MissingPKCE"} +{"Time":"2026-10-18T01:46:18.13441127Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_MissingPKCE","Output":"=== RUN TestNegative_MissingPKCE\n"} +{"Time":"2026-10-18T01:46:18.137013589Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_MissingPKCE","Output":"--- PASS: TestNegative_MissingPKCE (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.137066223Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_MissingPKCE","Elapsed":0} +{"Time":"2026-10-18T01:46:18.137075871Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_PKCEPlainMethod"} +{"Time":"2026-10-18T01:46:18.137084212Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_PKCEPlainMethod","Output":"=== RUN TestNegative_PKCEPlainMethod\n"} +{"Time":"2026-10-18T01:46:18.139836211Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_PKCEPlainMethod","Output":"--- PASS: TestNegative_PKCEPlainMethod (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.139889917Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_PKCEPlainMethod","Elapsed":0} +{"Time":"2026-10-18T01:46:18.139897573Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_UnknownGrantType"} +{"Time":"2026-10-18T01:46:18.139916284Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_UnknownGrantType","Output":"=== RUN TestNegative_UnknownGrantType\n"} +{"Time":"2026-10-18T01:46:18.142289791Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_UnknownGrantType","Output":"--- PASS: TestNegative_UnknownGrantType (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.142362515Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_UnknownGrantType","Elapsed":0} +{"Time":"2026-10-18T01:46:18.142373661Z","Action":"run","Package":"keycape/tests/negative","Test":"TestNegative_ValidRequest_PassesThrough"} +{"Time":"2026-10-18T01:46:18.142378061Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_ValidRequest_PassesThrough","Output":"=== RUN TestNegative_ValidRequest_PassesThrough\n"} +{"Time":"2026-10-18T01:46:18.144998552Z","Action":"output","Package":"keycape/tests/negative","Test":"TestNegative_ValidRequest_PassesThrough","Output":"--- PASS: TestNegative_ValidRequest_PassesThrough (0.00s)\n"} +{"Time":"2026-10-18T01:46:18.145056479Z","Action":"pass","Package":"keycape/tests/negative","Test":"TestNegative_ValidRequest_PassesThrough","Elapsed":0} +{"Time":"2026-10-18T01:46:18.145064847Z","Action":"output","Package":"keycape/tests/negative","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:18.147224384Z","Action":"output","Package":"keycape/tests/conformance","Output":"ok \tkeycape/tests/conformance\t1.025s\n"} +{"Time":"2026-10-18T01:46:18.147273816Z","Action":"pass","Package":"keycape/tests/conformance","Elapsed":1.025} +{"Time":"2026-10-18T01:46:18.150071373Z","Action":"start","Package":"keycape/tests/profile"} +{"Time":"2026-10-18T01:46:18.166222226Z","Action":"run","Package":"keycape/tests/profile","Test":"TestDiscovery"} +{"Time":"2026-10-18T01:46:18.166292146Z","Action":"output","Package":"keycape/tests/profile","Test":"TestDiscovery","Output":"=== RUN TestDiscovery\n"} +{"Time":"2026-10-18T01:46:18.19444146Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/correct_secret","Output":"--- PASS: TestConfidentialCodeClientRequiresItsSecret/correct_secret (0.09s)\n"} +{"Time":"2026-10-18T01:46:18.194496834Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret/correct_secret","Elapsed":0.09} +{"Time":"2026-10-18T01:46:18.19450633Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret","Output":"--- PASS: TestConfidentialCodeClientRequiresItsSecret (0.54s)\n"} +{"Time":"2026-10-18T01:46:18.194619126Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestConfidentialCodeClientRequiresItsSecret","Elapsed":0.54} +{"Time":"2026-10-18T01:46:18.194630822Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected"} +{"Time":"2026-10-18T01:46:18.194635204Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected","Output":"=== RUN TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected\n"} +{"Time":"2026-10-18T01:46:18.292973541Z","Action":"output","Package":"keycape/tests/migration","Output":"ok \tkeycape/tests/migration\t1.025s\n"} +{"Time":"2026-10-18T01:46:18.293022604Z","Action":"pass","Package":"keycape/tests/migration","Elapsed":1.025} +{"Time":"2026-10-18T01:46:18.507525537Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected","Output":"--- PASS: TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected (0.31s)\n"} +{"Time":"2026-10-18T01:46:18.507582481Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPublicClientSecretIsIgnoredAndEmptyConfidentialSecretIsRejected","Elapsed":0.31} +{"Time":"2026-10-18T01:46:18.507623203Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestConcurrentCodeExchangeSucceedsExactlyOnce"} +{"Time":"2026-10-18T01:46:18.507633609Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConcurrentCodeExchangeSucceedsExactlyOnce","Output":"=== RUN TestConcurrentCodeExchangeSucceedsExactlyOnce\n"} +{"Time":"2026-10-18T01:46:18.54651549Z","Action":"output","Package":"keycape/tests/profile","Test":"TestDiscovery","Output":"--- PASS: TestDiscovery (0.38s)\n"} +{"Time":"2026-10-18T01:46:18.546560829Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestDiscovery","Elapsed":0.38} +{"Time":"2026-10-18T01:46:18.546571397Z","Action":"run","Package":"keycape/tests/profile","Test":"TestJWKS"} +{"Time":"2026-10-18T01:46:18.546575274Z","Action":"output","Package":"keycape/tests/profile","Test":"TestJWKS","Output":"=== RUN TestJWKS\n"} +{"Time":"2026-10-18T01:46:18.561456251Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestConcurrentCodeExchangeSucceedsExactlyOnce","Output":"--- PASS: TestConcurrentCodeExchangeSucceedsExactlyOnce (0.05s)\n"} +{"Time":"2026-10-18T01:46:18.561512537Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestConcurrentCodeExchangeSucceedsExactlyOnce","Elapsed":0.05} +{"Time":"2026-10-18T01:46:18.561521662Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestFailedExchangeConsumesTheCode"} +{"Time":"2026-10-18T01:46:18.561526856Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestFailedExchangeConsumesTheCode","Output":"=== RUN TestFailedExchangeConsumesTheCode\n"} +{"Time":"2026-10-18T01:46:18.724517607Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestFailedExchangeConsumesTheCode","Output":"--- PASS: TestFailedExchangeConsumesTheCode (0.16s)\n"} +{"Time":"2026-10-18T01:46:18.724574857Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestFailedExchangeConsumesTheCode","Elapsed":0.16} +{"Time":"2026-10-18T01:46:18.724728747Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes"} +{"Time":"2026-10-18T01:46:18.724755763Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes","Output":"=== RUN TestUserinfoRejectsUnverifiedTokenShapes\n"} +{"Time":"2026-10-18T01:46:18.724920174Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/accepts_a_well-formed_access_token"} +{"Time":"2026-10-18T01:46:18.724939958Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/accepts_a_well-formed_access_token","Output":"=== RUN TestUserinfoRejectsUnverifiedTokenShapes/accepts_a_well-formed_access_token\n"} +{"Time":"2026-10-18T01:46:18.769214205Z","Action":"output","Package":"keycape/tests/profile","Test":"TestJWKS","Output":"--- PASS: TestJWKS (0.22s)\n"} +{"Time":"2026-10-18T01:46:18.769288065Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestJWKS","Elapsed":0.22} +{"Time":"2026-10-18T01:46:18.769312619Z","Action":"run","Package":"keycape/tests/profile","Test":"TestAuthorize_Redirect"} +{"Time":"2026-10-18T01:46:18.769319788Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_Redirect","Output":"=== RUN TestAuthorize_Redirect\n"} +{"Time":"2026-10-18T01:46:18.827003698Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/accepts_a_well-formed_access_token","Output":"--- PASS: TestUserinfoRejectsUnverifiedTokenShapes/accepts_a_well-formed_access_token (0.10s)\n"} +{"Time":"2026-10-18T01:46:18.827222078Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/accepts_a_well-formed_access_token","Elapsed":0.1} +{"Time":"2026-10-18T01:46:18.827238931Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/foreign_issuer"} +{"Time":"2026-10-18T01:46:18.827246794Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/foreign_issuer","Output":"=== RUN TestUserinfoRejectsUnverifiedTokenShapes/foreign_issuer\n"} +{"Time":"2026-10-18T01:46:18.895320873Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/foreign_issuer","Output":"--- PASS: TestUserinfoRejectsUnverifiedTokenShapes/foreign_issuer (0.07s)\n"} +{"Time":"2026-10-18T01:46:18.895698299Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/foreign_issuer","Elapsed":0.07} +{"Time":"2026-10-18T01:46:18.89576334Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/missing_issuer"} +{"Time":"2026-10-18T01:46:18.895771898Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/missing_issuer","Output":"=== RUN TestUserinfoRejectsUnverifiedTokenShapes/missing_issuer\n"} +{"Time":"2026-10-18T01:46:19.004134892Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_Redirect","Output":"--- PASS: TestAuthorize_Redirect (0.23s)\n"} +{"Time":"2026-10-18T01:46:19.004184351Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestAuthorize_Redirect","Elapsed":0.23} +{"Time":"2026-10-18T01:46:19.004197553Z","Action":"run","Package":"keycape/tests/profile","Test":"TestAuthorize_InvalidClient"} +{"Time":"2026-10-18T01:46:19.004203387Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_InvalidClient","Output":"=== RUN TestAuthorize_InvalidClient\n"} +{"Time":"2026-10-18T01:46:19.005878882Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/missing_issuer","Output":"--- PASS: TestUserinfoRejectsUnverifiedTokenShapes/missing_issuer (0.11s)\n"} +{"Time":"2026-10-18T01:46:19.005901281Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/missing_issuer","Elapsed":0.11} +{"Time":"2026-10-18T01:46:19.005937016Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/id_token_presented_as_access_token"} +{"Time":"2026-10-18T01:46:19.005943229Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/id_token_presented_as_access_token","Output":"=== RUN TestUserinfoRejectsUnverifiedTokenShapes/id_token_presented_as_access_token\n"} +{"Time":"2026-10-18T01:46:19.112890394Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_InvalidClient","Output":"--- PASS: TestAuthorize_InvalidClient (0.11s)\n"} +{"Time":"2026-10-18T01:46:19.112937752Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestAuthorize_InvalidClient","Elapsed":0.11} +{"Time":"2026-10-18T01:46:19.112947536Z","Action":"run","Package":"keycape/tests/profile","Test":"TestAuthorize_WildcardRedirectURI"} +{"Time":"2026-10-18T01:46:19.112951657Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_WildcardRedirectURI","Output":"=== RUN TestAuthorize_WildcardRedirectURI\n"} +{"Time":"2026-10-18T01:46:19.150614893Z","Action":"output","Package":"keycape/tests/negative","Output":"ok \tkeycape/tests/negative\t1.046s\n"} +{"Time":"2026-10-18T01:46:19.150678528Z","Action":"pass","Package":"keycape/tests/negative","Elapsed":1.046} +{"Time":"2026-10-18T01:46:19.210618153Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_WildcardRedirectURI","Output":"--- PASS: TestAuthorize_WildcardRedirectURI (0.10s)\n"} +{"Time":"2026-10-18T01:46:19.210748236Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestAuthorize_WildcardRedirectURI","Elapsed":0.1} +{"Time":"2026-10-18T01:46:19.210908936Z","Action":"run","Package":"keycape/tests/profile","Test":"TestAuthorize_MissingPKCE"} +{"Time":"2026-10-18T01:46:19.21092495Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_MissingPKCE","Output":"=== RUN TestAuthorize_MissingPKCE\n"} +{"Time":"2026-10-18T01:46:19.290098908Z","Action":"output","Package":"keycape/tests/profile","Test":"TestAuthorize_MissingPKCE","Output":"--- PASS: TestAuthorize_MissingPKCE (0.08s)\n"} +{"Time":"2026-10-18T01:46:19.29014642Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestAuthorize_MissingPKCE","Elapsed":0.08} +{"Time":"2026-10-18T01:46:19.290170451Z","Action":"run","Package":"keycape/tests/profile","Test":"TestHealthz"} +{"Time":"2026-10-18T01:46:19.290181867Z","Action":"output","Package":"keycape/tests/profile","Test":"TestHealthz","Output":"=== RUN TestHealthz\n"} +{"Time":"2026-10-18T01:46:19.359263725Z","Action":"output","Package":"keycape/tests/profile","Test":"TestHealthz","Output":"--- PASS: TestHealthz (0.07s)\n"} +{"Time":"2026-10-18T01:46:19.359319334Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestHealthz","Elapsed":0.07} +{"Time":"2026-10-18T01:46:19.359342058Z","Action":"run","Package":"keycape/tests/profile","Test":"TestCompleteTokenFlow"} +{"Time":"2026-10-18T01:46:19.359356341Z","Action":"output","Package":"keycape/tests/profile","Test":"TestCompleteTokenFlow","Output":"=== RUN TestCompleteTokenFlow\n"} +{"Time":"2026-10-18T01:46:19.517504832Z","Action":"output","Package":"keycape/tests/profile","Test":"TestCompleteTokenFlow","Output":"--- PASS: TestCompleteTokenFlow (0.16s)\n"} +{"Time":"2026-10-18T01:46:19.517554626Z","Action":"pass","Package":"keycape/tests/profile","Test":"TestCompleteTokenFlow","Elapsed":0.16} +{"Time":"2026-10-18T01:46:19.517567493Z","Action":"output","Package":"keycape/tests/profile","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:19.543781907Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/id_token_presented_as_access_token","Output":"--- PASS: TestUserinfoRejectsUnverifiedTokenShapes/id_token_presented_as_access_token (0.54s)\n"} +{"Time":"2026-10-18T01:46:19.543834009Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes/id_token_presented_as_access_token","Elapsed":0.54} +{"Time":"2026-10-18T01:46:19.543876417Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes","Output":"--- PASS: TestUserinfoRejectsUnverifiedTokenShapes (0.82s)\n"} +{"Time":"2026-10-18T01:46:19.544012802Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsUnverifiedTokenShapes","Elapsed":0.82} +{"Time":"2026-10-18T01:46:19.544029565Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsNonRS256Algorithms"} +{"Time":"2026-10-18T01:46:19.544035435Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsNonRS256Algorithms","Output":"=== RUN TestUserinfoRejectsNonRS256Algorithms\n"} +{"Time":"2026-10-18T01:46:19.658931003Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsNonRS256Algorithms","Output":"--- PASS: TestUserinfoRejectsNonRS256Algorithms (0.11s)\n"} +{"Time":"2026-10-18T01:46:19.659004097Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoRejectsNonRS256Algorithms","Elapsed":0.11} +{"Time":"2026-10-18T01:46:19.659022627Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ResponseCode"} +{"Time":"2026-10-18T01:46:19.659031852Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ResponseCode","Output":"=== RUN TestJWKSHandler_ResponseCode\n"} +{"Time":"2026-10-18T01:46:19.942129838Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ResponseCode","Output":"--- PASS: TestJWKSHandler_ResponseCode (0.28s)\n"} +{"Time":"2026-10-18T01:46:19.942186845Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ResponseCode","Elapsed":0.28} +{"Time":"2026-10-18T01:46:19.942198017Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ContentType"} +{"Time":"2026-10-18T01:46:19.942222084Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ContentType","Output":"=== RUN TestJWKSHandler_ContentType\n"} +{"Time":"2026-10-18T01:46:20.061341637Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ContentType","Output":"--- PASS: TestJWKSHandler_ContentType (0.12s)\n"} +{"Time":"2026-10-18T01:46:20.061400799Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_ContentType","Elapsed":0.12} +{"Time":"2026-10-18T01:46:20.061424622Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_StructureValid"} +{"Time":"2026-10-18T01:46:20.061431467Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_StructureValid","Output":"=== RUN TestJWKSHandler_StructureValid\n"} +{"Time":"2026-10-18T01:46:20.10741722Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_StructureValid","Output":"--- PASS: TestJWKSHandler_StructureValid (0.05s)\n"} +{"Time":"2026-10-18T01:46:20.107480954Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_StructureValid","Elapsed":0.05} +{"Time":"2026-10-18T01:46:20.107496364Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_CorrectAlgorithmFields"} +{"Time":"2026-10-18T01:46:20.107513344Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_CorrectAlgorithmFields","Output":"=== RUN TestJWKSHandler_CorrectAlgorithmFields\n"} +{"Time":"2026-10-18T01:46:20.247041068Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_CorrectAlgorithmFields","Output":"--- PASS: TestJWKSHandler_CorrectAlgorithmFields (0.14s)\n"} +{"Time":"2026-10-18T01:46:20.247112683Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_CorrectAlgorithmFields","Elapsed":0.14} +{"Time":"2026-10-18T01:46:20.247124144Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_MultipleKeys"} +{"Time":"2026-10-18T01:46:20.247137183Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_MultipleKeys","Output":"=== RUN TestJWKSHandler_MultipleKeys\n"} +{"Time":"2026-10-18T01:46:20.489456882Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_MultipleKeys","Output":"--- PASS: TestJWKSHandler_MultipleKeys (0.24s)\n"} +{"Time":"2026-10-18T01:46:20.489537947Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_MultipleKeys","Elapsed":0.24} +{"Time":"2026-10-18T01:46:20.489550635Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_Valid"} +{"Time":"2026-10-18T01:46:20.489557785Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_Valid","Output":"=== RUN TestLoadPublicKeyFromPEM_Valid\n"} +{"Time":"2026-10-18T01:46:20.524572703Z","Action":"output","Package":"keycape/tests/profile","Output":"ok \tkeycape/tests/profile\t2.374s\n"} +{"Time":"2026-10-18T01:46:20.524627122Z","Action":"pass","Package":"keycape/tests/profile","Elapsed":2.375} +{"Time":"2026-10-18T01:46:20.538770406Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_Valid","Output":"--- PASS: TestLoadPublicKeyFromPEM_Valid (0.05s)\n"} +{"Time":"2026-10-18T01:46:20.538847097Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_Valid","Elapsed":0.05} +{"Time":"2026-10-18T01:46:20.538859135Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_InvalidPEM"} +{"Time":"2026-10-18T01:46:20.538865772Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_InvalidPEM","Output":"=== RUN TestLoadPublicKeyFromPEM_InvalidPEM\n"} +{"Time":"2026-10-18T01:46:20.539111303Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_InvalidPEM","Output":"--- PASS: TestLoadPublicKeyFromPEM_InvalidPEM (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.539265263Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_InvalidPEM","Elapsed":0} +{"Time":"2026-10-18T01:46:20.539302919Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_PrivateKeyRejected"} +{"Time":"2026-10-18T01:46:20.539315264Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_PrivateKeyRejected","Output":"=== RUN TestLoadPublicKeyFromPEM_PrivateKeyRejected\n"} +{"Time":"2026-10-18T01:46:20.626777212Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_PrivateKeyRejected","Output":"--- PASS: TestLoadPublicKeyFromPEM_PrivateKeyRejected (0.09s)\n"} +{"Time":"2026-10-18T01:46:20.62684394Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestLoadPublicKeyFromPEM_PrivateKeyRejected","Elapsed":0.09} +{"Time":"2026-10-18T01:46:20.626860414Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_NEncoding"} +{"Time":"2026-10-18T01:46:20.626865166Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_NEncoding","Output":"=== RUN TestJWKSHandler_NEncoding\n"} +{"Time":"2026-10-18T01:46:20.715653675Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_NEncoding","Output":"--- PASS: TestJWKSHandler_NEncoding (0.09s)\n"} +{"Time":"2026-10-18T01:46:20.715738708Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestJWKSHandler_NEncoding","Elapsed":0.09} +{"Time":"2026-10-18T01:46:20.715756156Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestNativeOptionalEnrollmentAndOldSession"} +{"Time":"2026-10-18T01:46:20.715762694Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestNativeOptionalEnrollmentAndOldSession","Output":"=== RUN TestNativeOptionalEnrollmentAndOldSession\n"} +{"Time":"2026-10-18T01:46:20.715775552Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestNativeOptionalEnrollmentAndOldSession","Output":" native_optional_test.go:27: requires disposable installed-provider fixture\n"} +{"Time":"2026-10-18T01:46:20.715786229Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestNativeOptionalEnrollmentAndOldSession","Output":"--- SKIP: TestNativeOptionalEnrollmentAndOldSession (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.715794417Z","Action":"skip","Package":"keycape/internal/server/oidc","Test":"TestNativeOptionalEnrollmentAndOldSession","Elapsed":0} +{"Time":"2026-10-18T01:46:20.715802108Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment"} +{"Time":"2026-10-18T01:46:20.715808646Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment","Output":"=== RUN TestOptionalMFAFollowsEnrollment\n"} +{"Time":"2026-10-18T01:46:20.715822181Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/unenrolled_despite_global_requirement"} +{"Time":"2026-10-18T01:46:20.715830531Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/unenrolled_despite_global_requirement","Output":"=== RUN TestOptionalMFAFollowsEnrollment/unenrolled_despite_global_requirement\n"} +{"Time":"2026-10-18T01:46:20.716128578Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/unenrolled_despite_global_requirement","Output":"--- PASS: TestOptionalMFAFollowsEnrollment/unenrolled_despite_global_requirement (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.716146026Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/unenrolled_despite_global_requirement","Elapsed":0} +{"Time":"2026-10-18T01:46:20.716153709Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/enrolled"} +{"Time":"2026-10-18T01:46:20.716163682Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/enrolled","Output":"=== RUN TestOptionalMFAFollowsEnrollment/enrolled\n"} +{"Time":"2026-10-18T01:46:20.716689496Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/enrolled","Output":"--- PASS: TestOptionalMFAFollowsEnrollment/enrolled (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.716811791Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/enrolled","Elapsed":0} +{"Time":"2026-10-18T01:46:20.71683988Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/lookup_unavailable"} +{"Time":"2026-10-18T01:46:20.716846887Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/lookup_unavailable","Output":"=== RUN TestOptionalMFAFollowsEnrollment/lookup_unavailable\n"} +{"Time":"2026-10-18T01:46:20.717208105Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/lookup_unavailable","Output":"--- PASS: TestOptionalMFAFollowsEnrollment/lookup_unavailable (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.717231044Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/lookup_unavailable","Elapsed":0} +{"Time":"2026-10-18T01:46:20.717377142Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/explicit_step_up"} +{"Time":"2026-10-18T01:46:20.717391218Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/explicit_step_up","Output":"=== RUN TestOptionalMFAFollowsEnrollment/explicit_step_up\n"} +{"Time":"2026-10-18T01:46:20.71774308Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/explicit_step_up","Output":"--- PASS: TestOptionalMFAFollowsEnrollment/explicit_step_up (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.717758517Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment/explicit_step_up","Elapsed":0} +{"Time":"2026-10-18T01:46:20.717891197Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment","Output":"--- PASS: TestOptionalMFAFollowsEnrollment (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.71790686Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestOptionalMFAFollowsEnrollment","Elapsed":0} +{"Time":"2026-10-18T01:46:20.717916022Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPolicy_CoulombSocialPasswordOnlyWhenNoStrongerRule"} +{"Time":"2026-10-18T01:46:20.717921549Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_CoulombSocialPasswordOnlyWhenNoStrongerRule","Output":"=== RUN TestPolicy_CoulombSocialPasswordOnlyWhenNoStrongerRule\n"} +{"Time":"2026-10-18T01:46:20.718270724Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_CoulombSocialPasswordOnlyWhenNoStrongerRule","Output":"--- PASS: TestPolicy_CoulombSocialPasswordOnlyWhenNoStrongerRule (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.718286288Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPolicy_CoulombSocialPasswordOnlyWhenNoStrongerRule","Elapsed":0} +{"Time":"2026-10-18T01:46:20.718295368Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ProfileActionStepUpForcesMFA"} +{"Time":"2026-10-18T01:46:20.718300905Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ProfileActionStepUpForcesMFA","Output":"=== RUN TestPolicy_ProfileActionStepUpForcesMFA\n"} +{"Time":"2026-10-18T01:46:20.718722236Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ProfileActionStepUpForcesMFA","Output":"--- PASS: TestPolicy_ProfileActionStepUpForcesMFA (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.718770749Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ProfileActionStepUpForcesMFA","Elapsed":0} +{"Time":"2026-10-18T01:46:20.718814645Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPolicy_OpenBaoKeepsMandatoryMFA"} +{"Time":"2026-10-18T01:46:20.718827188Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_OpenBaoKeepsMandatoryMFA","Output":"=== RUN TestPolicy_OpenBaoKeepsMandatoryMFA\n"} +{"Time":"2026-10-18T01:46:20.719219405Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_OpenBaoKeepsMandatoryMFA","Output":"--- PASS: TestPolicy_OpenBaoKeepsMandatoryMFA (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.719251471Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPolicy_OpenBaoKeepsMandatoryMFA","Elapsed":0} +{"Time":"2026-10-18T01:46:20.719354844Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPolicy_LowAssuranceClientDoesNotSuppressHighAssurance"} +{"Time":"2026-10-18T01:46:20.719364763Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_LowAssuranceClientDoesNotSuppressHighAssurance","Output":"=== RUN TestPolicy_LowAssuranceClientDoesNotSuppressHighAssurance\n"} +{"Time":"2026-10-18T01:46:20.720246065Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_LowAssuranceClientDoesNotSuppressHighAssurance","Output":"--- PASS: TestPolicy_LowAssuranceClientDoesNotSuppressHighAssurance (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.720289673Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPolicy_LowAssuranceClientDoesNotSuppressHighAssurance","Elapsed":0} +{"Time":"2026-10-18T01:46:20.720300394Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPolicy_NoFactorEnrollmentHandoffDoesNotBypass"} +{"Time":"2026-10-18T01:46:20.720312432Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_NoFactorEnrollmentHandoffDoesNotBypass","Output":"=== RUN TestPolicy_NoFactorEnrollmentHandoffDoesNotBypass\n"} +{"Time":"2026-10-18T01:46:20.720819035Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_NoFactorEnrollmentHandoffDoesNotBypass","Output":"--- PASS: TestPolicy_NoFactorEnrollmentHandoffDoesNotBypass (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.720853877Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPolicy_NoFactorEnrollmentHandoffDoesNotBypass","Elapsed":0} +{"Time":"2026-10-18T01:46:20.720864662Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ExactRedirectStillEnforced"} +{"Time":"2026-10-18T01:46:20.720871028Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ExactRedirectStillEnforced","Output":"=== RUN TestPolicy_ExactRedirectStillEnforced\n"} +{"Time":"2026-10-18T01:46:20.72117472Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ExactRedirectStillEnforced","Output":"--- PASS: TestPolicy_ExactRedirectStillEnforced (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.721194197Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestPolicy_ExactRedirectStillEnforced","Elapsed":0} +{"Time":"2026-10-18T01:46:20.721203747Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestLogout_ClearsSessionSoHighAssuranceRequiresMFAAgain"} +{"Time":"2026-10-18T01:46:20.721209067Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLogout_ClearsSessionSoHighAssuranceRequiresMFAAgain","Output":"=== RUN TestLogout_ClearsSessionSoHighAssuranceRequiresMFAAgain\n"} +{"Time":"2026-10-18T01:46:20.721854837Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLogout_ClearsSessionSoHighAssuranceRequiresMFAAgain","Output":"--- PASS: TestLogout_ClearsSessionSoHighAssuranceRequiresMFAAgain (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.721872195Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestLogout_ClearsSessionSoHighAssuranceRequiresMFAAgain","Elapsed":0} +{"Time":"2026-10-18T01:46:20.721885045Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestLogout_RejectsUnregisteredPostLogoutRedirect"} +{"Time":"2026-10-18T01:46:20.721890311Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLogout_RejectsUnregisteredPostLogoutRedirect","Output":"=== RUN TestLogout_RejectsUnregisteredPostLogoutRedirect\n"} +{"Time":"2026-10-18T01:46:20.722195383Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestLogout_RejectsUnregisteredPostLogoutRedirect","Output":"--- PASS: TestLogout_RejectsUnregisteredPostLogoutRedirect (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.722211334Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestLogout_RejectsUnregisteredPostLogoutRedirect","Elapsed":0} +{"Time":"2026-10-18T01:46:20.722223787Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestRuntimePolicyEnrollmentOldSessionAndStepUp"} +{"Time":"2026-10-18T01:46:20.722228809Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestRuntimePolicyEnrollmentOldSessionAndStepUp","Output":"=== RUN TestRuntimePolicyEnrollmentOldSessionAndStepUp\n"} +{"Time":"2026-10-18T01:46:20.724695748Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestRuntimePolicyEnrollmentOldSessionAndStepUp","Output":"--- PASS: TestRuntimePolicyEnrollmentOldSessionAndStepUp (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.724748229Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestRuntimePolicyEnrollmentOldSessionAndStepUp","Elapsed":0} +{"Time":"2026-10-18T01:46:20.724759753Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOff_RedirectUnchanged"} +{"Time":"2026-10-18T01:46:20.724765542Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOff_RedirectUnchanged","Output":"=== RUN TestSignInDiagnosis_FlagOff_RedirectUnchanged\n"} +{"Time":"2026-10-18T01:46:20.725193888Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOff_RedirectUnchanged","Output":"--- PASS: TestSignInDiagnosis_FlagOff_RedirectUnchanged (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.725222121Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOff_RedirectUnchanged","Elapsed":0} +{"Time":"2026-10-18T01:46:20.725361636Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOn_ShowsFieldsAndContinue"} +{"Time":"2026-10-18T01:46:20.725375829Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOn_ShowsFieldsAndContinue","Output":"=== RUN TestSignInDiagnosis_FlagOn_ShowsFieldsAndContinue\n"} +{"Time":"2026-10-18T01:46:20.726242297Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOn_ShowsFieldsAndContinue","Output":"--- PASS: TestSignInDiagnosis_FlagOn_ShowsFieldsAndContinue (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.726265417Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_FlagOn_ShowsFieldsAndContinue","Elapsed":0} +{"Time":"2026-10-18T01:46:20.726273866Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AAL2WhenThisRequestVerifiedOTP"} +{"Time":"2026-10-18T01:46:20.726280052Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AAL2WhenThisRequestVerifiedOTP","Output":"=== RUN TestSignInDiagnosis_AAL2WhenThisRequestVerifiedOTP\n"} +{"Time":"2026-10-18T01:46:20.726842629Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AAL2WhenThisRequestVerifiedOTP","Output":"--- PASS: TestSignInDiagnosis_AAL2WhenThisRequestVerifiedOTP (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.726873549Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AAL2WhenThisRequestVerifiedOTP","Elapsed":0} +{"Time":"2026-10-18T01:46:20.726881692Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_ReusedAAL2SessionIsAAL1ForThisRequest"} +{"Time":"2026-10-18T01:46:20.726887418Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_ReusedAAL2SessionIsAAL1ForThisRequest","Output":"=== RUN TestSignInDiagnosis_ReusedAAL2SessionIsAAL1ForThisRequest\n"} +{"Time":"2026-10-18T01:46:20.727296878Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_ReusedAAL2SessionIsAAL1ForThisRequest","Output":"--- PASS: TestSignInDiagnosis_ReusedAAL2SessionIsAAL1ForThisRequest (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.72732631Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_ReusedAAL2SessionIsAAL1ForThisRequest","Elapsed":0} +{"Time":"2026-10-18T01:46:20.727333786Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AuthLogLineHasFieldsAndNoSecrets"} +{"Time":"2026-10-18T01:46:20.727339214Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AuthLogLineHasFieldsAndNoSecrets","Output":"=== RUN TestSignInDiagnosis_AuthLogLineHasFieldsAndNoSecrets\n"} +{"Time":"2026-10-18T01:46:20.727575461Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AuthLogLineHasFieldsAndNoSecrets","Output":"--- PASS: TestSignInDiagnosis_AuthLogLineHasFieldsAndNoSecrets (0.00s)\n"} +{"Time":"2026-10-18T01:46:20.727601007Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestSignInDiagnosis_AuthLogLineHasFieldsAndNoSecrets","Elapsed":0} +{"Time":"2026-10-18T01:46:20.727614686Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestRegistrationBoundTenantRequiresStaticRegistration"} +{"Time":"2026-10-18T01:46:20.727620142Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestRegistrationBoundTenantRequiresStaticRegistration","Output":"=== RUN TestRegistrationBoundTenantRequiresStaticRegistration\n"} +{"Time":"2026-10-18T01:46:21.15827548Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestRegistrationBoundTenantRequiresStaticRegistration","Output":"--- PASS: TestRegistrationBoundTenantRequiresStaticRegistration (0.43s)\n"} +{"Time":"2026-10-18T01:46:21.158349052Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestRegistrationBoundTenantRequiresStaticRegistration","Elapsed":0.43} +{"Time":"2026-10-18T01:46:21.158433616Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance"} +{"Time":"2026-10-18T01:46:21.158470054Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance","Output":"=== RUN TestIssuedTokensCarryTenantProvenance\n"} +{"Time":"2026-10-18T01:46:21.158498242Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_registration-supplied"} +{"Time":"2026-10-18T01:46:21.158509856Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_registration-supplied","Output":"=== RUN TestIssuedTokensCarryTenantProvenance/human,_registration-supplied\n"} +{"Time":"2026-10-18T01:46:21.215363918Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_registration-supplied","Output":"--- PASS: TestIssuedTokensCarryTenantProvenance/human,_registration-supplied (0.06s)\n"} +{"Time":"2026-10-18T01:46:21.215423576Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_registration-supplied","Elapsed":0.06} +{"Time":"2026-10-18T01:46:21.215437354Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_directory-asserted"} +{"Time":"2026-10-18T01:46:21.215443089Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_directory-asserted","Output":"=== RUN TestIssuedTokensCarryTenantProvenance/human,_directory-asserted\n"} +{"Time":"2026-10-18T01:46:21.303866009Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_directory-asserted","Output":"--- PASS: TestIssuedTokensCarryTenantProvenance/human,_directory-asserted (0.09s)\n"} +{"Time":"2026-10-18T01:46:21.303959987Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_directory-asserted","Elapsed":0.09} +{"Time":"2026-10-18T01:46:21.303976137Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_nobody_asserted"} +{"Time":"2026-10-18T01:46:21.303985001Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_nobody_asserted","Output":"=== RUN TestIssuedTokensCarryTenantProvenance/human,_nobody_asserted\n"} +{"Time":"2026-10-18T01:46:21.419021982Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_nobody_asserted","Output":"--- PASS: TestIssuedTokensCarryTenantProvenance/human,_nobody_asserted (0.11s)\n"} +{"Time":"2026-10-18T01:46:21.419080134Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/human,_nobody_asserted","Elapsed":0.11} +{"Time":"2026-10-18T01:46:21.419092145Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/service_is_always_registration-supplied"} +{"Time":"2026-10-18T01:46:21.419099152Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/service_is_always_registration-supplied","Output":"=== RUN TestIssuedTokensCarryTenantProvenance/service_is_always_registration-supplied\n"} +{"Time":"2026-10-18T01:46:21.603628344Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/service_is_always_registration-supplied","Output":"--- PASS: TestIssuedTokensCarryTenantProvenance/service_is_always_registration-supplied (0.18s)\n"} +{"Time":"2026-10-18T01:46:21.603688236Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance/service_is_always_registration-supplied","Elapsed":0.18} +{"Time":"2026-10-18T01:46:21.603706587Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance","Output":"--- PASS: TestIssuedTokensCarryTenantProvenance (0.45s)\n"} +{"Time":"2026-10-18T01:46:21.60373748Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestIssuedTokensCarryTenantProvenance","Elapsed":0.45} +{"Time":"2026-10-18T01:46:21.603757534Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters"} +{"Time":"2026-10-18T01:46:21.603765704Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters","Output":"=== RUN TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters\n"} +{"Time":"2026-10-18T01:46:21.687811217Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters","Output":"--- PASS: TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters (0.08s)\n"} +{"Time":"2026-10-18T01:46:21.687870307Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantIsBoundToRegistrationAndIgnoresRequestParameters","Elapsed":0.08} +{"Time":"2026-10-18T01:46:21.687882562Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantsAreDistinctPerRegistration"} +{"Time":"2026-10-18T01:46:21.68789038Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantsAreDistinctPerRegistration","Output":"=== RUN TestServiceTenantsAreDistinctPerRegistration\n"} +{"Time":"2026-10-18T01:46:21.886742591Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantsAreDistinctPerRegistration","Output":"--- PASS: TestServiceTenantsAreDistinctPerRegistration (0.20s)\n"} +{"Time":"2026-10-18T01:46:21.886818778Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestServiceTenantsAreDistinctPerRegistration","Elapsed":0.2} +{"Time":"2026-10-18T01:46:21.88685198Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault"} +{"Time":"2026-10-18T01:46:21.886859671Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault","Output":"=== RUN TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault\n"} +{"Time":"2026-10-18T01:46:21.886868878Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/directory"} +{"Time":"2026-10-18T01:46:21.886875794Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/directory","Output":"=== RUN TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/directory\n"} +{"Time":"2026-10-18T01:46:21.968936215Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/directory","Output":"--- PASS: TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/directory (0.08s)\n"} +{"Time":"2026-10-18T01:46:21.968993961Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/directory","Elapsed":0.08} +{"Time":"2026-10-18T01:46:21.969005792Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/default"} +{"Time":"2026-10-18T01:46:21.969012275Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/default","Output":"=== RUN TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/default\n"} +{"Time":"2026-10-18T01:46:22.021113485Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/default","Output":"--- PASS: TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/default (0.05s)\n"} +{"Time":"2026-10-18T01:46:22.021172909Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault/default","Elapsed":0.05} +{"Time":"2026-10-18T01:46:22.021189934Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault","Output":"--- PASS: TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault (0.13s)\n"} +{"Time":"2026-10-18T01:46:22.021199979Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestHumanTenantClaimUsesDirectoryValueThenPlatformDefault","Elapsed":0.13} +{"Time":"2026-10-18T01:46:22.021207869Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases"} +{"Time":"2026-10-18T01:46:22.021214966Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases","Output":"=== RUN TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases\n"} +{"Time":"2026-10-18T01:46:22.346771807Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases","Output":"--- PASS: TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases (0.33s)\n"} +{"Time":"2026-10-18T01:46:22.346830176Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases","Elapsed":0.33} +{"Time":"2026-10-18T01:46:22.346842088Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUnrelatedServiceClientKeepsCoulombTenant"} +{"Time":"2026-10-18T01:46:22.346856335Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUnrelatedServiceClientKeepsCoulombTenant","Output":"=== RUN TestUnrelatedServiceClientKeepsCoulombTenant\n"} +{"Time":"2026-10-18T01:46:22.473393266Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUnrelatedServiceClientKeepsCoulombTenant","Output":"--- PASS: TestUnrelatedServiceClientKeepsCoulombTenant (0.13s)\n"} +{"Time":"2026-10-18T01:46:22.473470859Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUnrelatedServiceClientKeepsCoulombTenant","Elapsed":0.13} +{"Time":"2026-10-18T01:46:22.473491779Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile"} +{"Time":"2026-10-18T01:46:22.473499516Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Output":"=== RUN TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile\n"} +{"Time":"2026-10-18T01:46:22.47400588Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Output":"--- PASS: TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile (0.00s)\n"} +{"Time":"2026-10-18T01:46:22.47402817Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile","Elapsed":0} +{"Time":"2026-10-18T01:46:22.474037287Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant"} +{"Time":"2026-10-18T01:46:22.474043996Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":"=== RUN TestTokenStatusActualIssueRevokeAndFreshGrant\n"} +{"Time":"2026-10-18T01:46:22.608905956Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Output":"--- PASS: TestTokenStatusActualIssueRevokeAndFreshGrant (0.13s)\n"} +{"Time":"2026-10-18T01:46:22.608966227Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualIssueRevokeAndFreshGrant","Elapsed":0.13} +{"Time":"2026-10-18T01:46:22.608977426Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority"} +{"Time":"2026-10-18T01:46:22.608984992Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Output":"=== RUN TestTokenStatusNoCrossClientOrIDTokenAuthority\n"} +{"Time":"2026-10-18T01:46:22.678527016Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Output":"--- PASS: TestTokenStatusNoCrossClientOrIDTokenAuthority (0.07s)\n"} +{"Time":"2026-10-18T01:46:22.678587332Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusNoCrossClientOrIDTokenAuthority","Elapsed":0.07} +{"Time":"2026-10-18T01:46:22.678599605Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken"} +{"Time":"2026-10-18T01:46:22.678606827Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Output":"=== RUN TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken\n"} +{"Time":"2026-10-18T01:46:22.85671266Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Output":"--- PASS: TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken (0.18s)\n"} +{"Time":"2026-10-18T01:46:22.856788197Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken","Elapsed":0.18} +{"Time":"2026-10-18T01:46:22.85680671Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse"} +{"Time":"2026-10-18T01:46:22.856812589Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Output":"=== RUN TestTokenStatusRestartAndUnavailableStoreRefuse\n"} +{"Time":"2026-10-18T01:46:22.895919195Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Output":"--- PASS: TestTokenStatusRestartAndUnavailableStoreRefuse (0.04s)\n"} +{"Time":"2026-10-18T01:46:22.895988059Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRestartAndUnavailableStoreRefuse","Elapsed":0.04} +{"Time":"2026-10-18T01:46:22.895997077Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition"} +{"Time":"2026-10-18T01:46:22.896001684Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition\n"} +{"Time":"2026-10-18T01:46:22.89603457Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed"} +{"Time":"2026-10-18T01:46:22.896039854Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed\n"} +{"Time":"2026-10-18T01:46:22.92740582Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed (0.03s)\n"} +{"Time":"2026-10-18T01:46:22.927464143Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/removed","Elapsed":0.03} +{"Time":"2026-10-18T01:46:22.927476091Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience"} +{"Time":"2026-10-18T01:46:22.927483702Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience\n"} +{"Time":"2026-10-18T01:46:23.016473666Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience (0.09s)\n"} +{"Time":"2026-10-18T01:46:23.016643578Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/audience","Elapsed":0.09} +{"Time":"2026-10-18T01:46:23.016671405Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret"} +{"Time":"2026-10-18T01:46:23.016681351Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret\n"} +{"Time":"2026-10-18T01:46:23.117680273Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret (0.10s)\n"} +{"Time":"2026-10-18T01:46:23.1177404Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/secret","Elapsed":0.1} +{"Time":"2026-10-18T01:46:23.117754692Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes"} +{"Time":"2026-10-18T01:46:23.117761455Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes\n"} +{"Time":"2026-10-18T01:46:23.362245396Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes (0.24s)\n"} +{"Time":"2026-10-18T01:46:23.362317742Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/scopes","Elapsed":0.24} +{"Time":"2026-10-18T01:46:23.362348085Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime"} +{"Time":"2026-10-18T01:46:23.362353342Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime\n"} +{"Time":"2026-10-18T01:46:23.386366339Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime (0.02s)\n"} +{"Time":"2026-10-18T01:46:23.386434924Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/lifetime","Elapsed":0.02} +{"Time":"2026-10-18T01:46:23.386446854Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended"} +{"Time":"2026-10-18T01:46:23.386454185Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended\n"} +{"Time":"2026-10-18T01:46:23.542314308Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended (0.16s)\n"} +{"Time":"2026-10-18T01:46:23.542371992Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/suspended","Elapsed":0.16} +{"Time":"2026-10-18T01:46:23.542385986Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled"} +{"Time":"2026-10-18T01:46:23.542392948Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled\n"} +{"Time":"2026-10-18T01:46:23.777455836Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled (0.23s)\n"} +{"Time":"2026-10-18T01:46:23.77751792Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/disabled","Elapsed":0.23} +{"Time":"2026-10-18T01:46:23.777530553Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed"} +{"Time":"2026-10-18T01:46:23.777538768Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Output":"=== RUN TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed\n"} +{"Time":"2026-10-18T01:46:23.925722356Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed (0.15s)\n"} +{"Time":"2026-10-18T01:46:23.925782871Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition/user-removed","Elapsed":0.15} +{"Time":"2026-10-18T01:46:23.925798795Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Output":"--- PASS: TestTokenStatusRegistrationDriftAndDirectoryDisposition (1.03s)\n"} +{"Time":"2026-10-18T01:46:23.925807524Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRegistrationDriftAndDirectoryDisposition","Elapsed":1.03} +{"Time":"2026-10-18T01:46:23.925830905Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect"} +{"Time":"2026-10-18T01:46:23.925838245Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":"=== RUN TestTokenStatusPublicClientMayRevokeButNotIntrospect\n"} +{"Time":"2026-10-18T01:46:24.106240131Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Output":"--- PASS: TestTokenStatusPublicClientMayRevokeButNotIntrospect (0.18s)\n"} +{"Time":"2026-10-18T01:46:24.106295831Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusPublicClientMayRevokeButNotIntrospect","Elapsed":0.18} +{"Time":"2026-10-18T01:46:24.106306787Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct"} +{"Time":"2026-10-18T01:46:24.106314037Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":"=== RUN TestTokenStatusServiceGrantIsCurrentAndDistinct\n"} +{"Time":"2026-10-18T01:46:24.149481356Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Output":"--- PASS: TestTokenStatusServiceGrantIsCurrentAndDistinct (0.04s)\n"} +{"Time":"2026-10-18T01:46:24.149631394Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusServiceGrantIsCurrentAndDistinct","Elapsed":0.04} +{"Time":"2026-10-18T01:46:24.149666309Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests"} +{"Time":"2026-10-18T01:46:24.149678446Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests\n"} +{"Time":"2026-10-18T01:46:24.149693667Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get"} +{"Time":"2026-10-18T01:46:24.149701693Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get\n"} +{"Time":"2026-10-18T01:46:24.247629361Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get (0.10s)\n"} +{"Time":"2026-10-18T01:46:24.247690931Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/get","Elapsed":0.1} +{"Time":"2026-10-18T01:46:24.247703762Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query"} +{"Time":"2026-10-18T01:46:24.247711454Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query\n"} +{"Time":"2026-10-18T01:46:24.302040177Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query (0.05s)\n"} +{"Time":"2026-10-18T01:46:24.302130954Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/query","Elapsed":0.05} +{"Time":"2026-10-18T01:46:24.302145995Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json"} +{"Time":"2026-10-18T01:46:24.302154922Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json\n"} +{"Time":"2026-10-18T01:46:24.390306618Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json (0.09s)\n"} +{"Time":"2026-10-18T01:46:24.390385726Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/json","Elapsed":0.09} +{"Time":"2026-10-18T01:46:24.39040836Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token"} +{"Time":"2026-10-18T01:46:24.390467206Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token\n"} +{"Time":"2026-10-18T01:46:24.521247273Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token (0.13s)\n"} +{"Time":"2026-10-18T01:46:24.521305191Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-token","Elapsed":0.13} +{"Time":"2026-10-18T01:46:24.521319943Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize"} +{"Time":"2026-10-18T01:46:24.521326823Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize\n"} +{"Time":"2026-10-18T01:46:24.735773236Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize (0.21s)\n"} +{"Time":"2026-10-18T01:46:24.735843986Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/oversize","Elapsed":0.21} +{"Time":"2026-10-18T01:46:24.735895664Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret"} +{"Time":"2026-10-18T01:46:24.735904591Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret\n"} +{"Time":"2026-10-18T01:46:24.943105273Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret (0.21s)\n"} +{"Time":"2026-10-18T01:46:24.943166347Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/wrong-secret","Elapsed":0.21} +{"Time":"2026-10-18T01:46:24.943179385Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth"} +{"Time":"2026-10-18T01:46:24.943187393Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth\n"} +{"Time":"2026-10-18T01:46:25.27072982Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth (0.33s)\n"} +{"Time":"2026-10-18T01:46:25.270796999Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/missing-auth","Elapsed":0.33} +{"Time":"2026-10-18T01:46:25.270828054Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth"} +{"Time":"2026-10-18T01:46:25.270834411Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth\n"} +{"Time":"2026-10-18T01:46:25.360050101Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth (0.09s)\n"} +{"Time":"2026-10-18T01:46:25.360104755Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/duplicate-auth","Elapsed":0.09} +{"Time":"2026-10-18T01:46:25.360114836Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret"} +{"Time":"2026-10-18T01:46:25.360121058Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret\n"} +{"Time":"2026-10-18T01:46:25.50814117Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret (0.15s)\n"} +{"Time":"2026-10-18T01:46:25.50821998Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/body-secret","Elapsed":0.15} +{"Time":"2026-10-18T01:46:25.508235715Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client"} +{"Time":"2026-10-18T01:46:25.508261622Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Output":"=== RUN TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client\n"} +{"Time":"2026-10-18T01:46:25.541759551Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client (0.03s)\n"} +{"Time":"2026-10-18T01:46:25.541819624Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests/mismatched-client","Elapsed":0.03} +{"Time":"2026-10-18T01:46:25.541836197Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Output":"--- PASS: TestTokenStatusRejectsMalformedOrUnauthenticatedRequests (1.39s)\n"} +{"Time":"2026-10-18T01:46:25.541844349Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRejectsMalformedOrUnauthenticatedRequests","Elapsed":1.39} +{"Time":"2026-10-18T01:46:25.541852239Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses"} +{"Time":"2026-10-18T01:46:25.54185994Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":"=== RUN TestTokenStatusRevocationDuringDirectoryLookupRefuses\n"} +{"Time":"2026-10-18T01:46:25.594491902Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Output":"--- PASS: TestTokenStatusRevocationDuringDirectoryLookupRefuses (0.05s)\n"} +{"Time":"2026-10-18T01:46:25.594558459Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringDirectoryLookupRefuses","Elapsed":0.05} +{"Time":"2026-10-18T01:46:25.594594861Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding"} +{"Time":"2026-10-18T01:46:25.594603121Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Output":"=== RUN TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding\n"} +{"Time":"2026-10-18T01:46:25.661517257Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Output":"--- PASS: TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding (0.07s)\n"} +{"Time":"2026-10-18T01:46:25.661563579Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding","Elapsed":0.07} +{"Time":"2026-10-18T01:46:25.661574589Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses"} +{"Time":"2026-10-18T01:46:25.661579494Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Output":"=== RUN TestTokenStatusRevocationDuringUserInfoLookupRefuses\n"} +{"Time":"2026-10-18T01:46:25.6867666Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Output":"--- PASS: TestTokenStatusRevocationDuringUserInfoLookupRefuses (0.02s)\n"} +{"Time":"2026-10-18T01:46:25.686823905Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusRevocationDuringUserInfoLookupRefuses","Elapsed":0.02} +{"Time":"2026-10-18T01:46:25.686839811Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim"} +{"Time":"2026-10-18T01:46:25.686847368Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim","Output":"=== RUN TestTokenStatusLegacyGrantHasNoStatusClaim\n"} +{"Time":"2026-10-18T01:46:25.814776511Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim","Output":"--- PASS: TestTokenStatusLegacyGrantHasNoStatusClaim (0.13s)\n"} +{"Time":"2026-10-18T01:46:25.814835673Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenStatusLegacyGrantHasNoStatusClaim","Elapsed":0.13} +{"Time":"2026-10-18T01:46:25.814847369Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ValidExchange_ReturnsJWT"} +{"Time":"2026-10-18T01:46:25.814854754Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ValidExchange_ReturnsJWT","Output":"=== RUN TestTokenHandler_ValidExchange_ReturnsJWT\n"} +{"Time":"2026-10-18T01:46:25.856525345Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ValidExchange_ReturnsJWT","Output":"--- PASS: TestTokenHandler_ValidExchange_ReturnsJWT (0.04s)\n"} +{"Time":"2026-10-18T01:46:25.856583137Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ValidExchange_ReturnsJWT","Elapsed":0.04} +{"Time":"2026-10-18T01:46:25.85659494Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_SuspendedUserCannotExchangeCode"} +{"Time":"2026-10-18T01:46:25.856609107Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_SuspendedUserCannotExchangeCode","Output":"=== RUN TestTokenHandler_SuspendedUserCannotExchangeCode\n"} +{"Time":"2026-10-18T01:46:26.021285539Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_SuspendedUserCannotExchangeCode","Output":"--- PASS: TestTokenHandler_SuspendedUserCannotExchangeCode (0.16s)\n"} +{"Time":"2026-10-18T01:46:26.021527876Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_SuspendedUserCannotExchangeCode","Elapsed":0.16} +{"Time":"2026-10-18T01:46:26.021568553Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_WrongGrantType_FeatureNotSupported"} +{"Time":"2026-10-18T01:46:26.021574288Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_WrongGrantType_FeatureNotSupported","Output":"=== RUN TestTokenHandler_WrongGrantType_FeatureNotSupported\n"} +{"Time":"2026-10-18T01:46:26.181659257Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_WrongGrantType_FeatureNotSupported","Output":"--- PASS: TestTokenHandler_WrongGrantType_FeatureNotSupported (0.16s)\n"} +{"Time":"2026-10-18T01:46:26.181718789Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_WrongGrantType_FeatureNotSupported","Elapsed":0.16} +{"Time":"2026-10-18T01:46:26.181730556Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_ReturnsScopedServiceToken"} +{"Time":"2026-10-18T01:46:26.181737626Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_ReturnsScopedServiceToken","Output":"=== RUN TestTokenHandler_ClientCredentials_ReturnsScopedServiceToken\n"} +{"Time":"2026-10-18T01:46:26.196575702Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_ReturnsScopedServiceToken","Output":"--- PASS: TestTokenHandler_ClientCredentials_ReturnsScopedServiceToken (0.01s)\n"} +{"Time":"2026-10-18T01:46:26.196628092Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_ReturnsScopedServiceToken","Elapsed":0.01} +{"Time":"2026-10-18T01:46:26.196639706Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_UsesPerClientLifetime"} +{"Time":"2026-10-18T01:46:26.196645396Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_UsesPerClientLifetime","Output":"=== RUN TestTokenHandler_ClientCredentials_UsesPerClientLifetime\n"} +{"Time":"2026-10-18T01:46:26.242013146Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_UsesPerClientLifetime","Output":"--- PASS: TestTokenHandler_ClientCredentials_UsesPerClientLifetime (0.05s)\n"} +{"Time":"2026-10-18T01:46:26.242067764Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_UsesPerClientLifetime","Elapsed":0.05} +{"Time":"2026-10-18T01:46:26.242077223Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsWrongSecret"} +{"Time":"2026-10-18T01:46:26.24208358Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsWrongSecret","Output":"=== RUN TestTokenHandler_ClientCredentials_RejectsWrongSecret\n"} +{"Time":"2026-10-18T01:46:26.490868139Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsWrongSecret","Output":"--- PASS: TestTokenHandler_ClientCredentials_RejectsWrongSecret (0.25s)\n"} +{"Time":"2026-10-18T01:46:26.49101329Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsWrongSecret","Elapsed":0.25} +{"Time":"2026-10-18T01:46:26.49104687Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsExcessScope"} +{"Time":"2026-10-18T01:46:26.491055193Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsExcessScope","Output":"=== RUN TestTokenHandler_ClientCredentials_RejectsExcessScope\n"} +{"Time":"2026-10-18T01:46:26.554016234Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsExcessScope","Output":"--- PASS: TestTokenHandler_ClientCredentials_RejectsExcessScope (0.06s)\n"} +{"Time":"2026-10-18T01:46:26.554074837Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ClientCredentials_RejectsExcessScope","Elapsed":0.06} +{"Time":"2026-10-18T01:46:26.554107497Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_PKCEMismatch_InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:26.554115748Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_PKCEMismatch_InvalidProfileUsage","Output":"=== RUN TestTokenHandler_PKCEMismatch_InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:26.596189892Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_PKCEMismatch_InvalidProfileUsage","Output":"--- PASS: TestTokenHandler_PKCEMismatch_InvalidProfileUsage (0.04s)\n"} +{"Time":"2026-10-18T01:46:26.596275556Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_PKCEMismatch_InvalidProfileUsage","Elapsed":0.04} +{"Time":"2026-10-18T01:46:26.596289956Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeNotFound_InvalidProfileUsage"} +{"Time":"2026-10-18T01:46:26.596298414Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeNotFound_InvalidProfileUsage","Output":"=== RUN TestTokenHandler_CodeNotFound_InvalidProfileUsage\n"} +{"Time":"2026-10-18T01:46:26.682414185Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeNotFound_InvalidProfileUsage","Output":"--- PASS: TestTokenHandler_CodeNotFound_InvalidProfileUsage (0.09s)\n"} +{"Time":"2026-10-18T01:46:26.682463815Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeNotFound_InvalidProfileUsage","Elapsed":0.09} +{"Time":"2026-10-18T01:46:26.682473617Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_JWTClaims_CorrectSubAndIssuer"} +{"Time":"2026-10-18T01:46:26.682478288Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_JWTClaims_CorrectSubAndIssuer","Output":"=== RUN TestTokenHandler_JWTClaims_CorrectSubAndIssuer\n"} +{"Time":"2026-10-18T01:46:26.817861621Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_JWTClaims_CorrectSubAndIssuer","Output":"--- PASS: TestTokenHandler_JWTClaims_CorrectSubAndIssuer (0.14s)\n"} +{"Time":"2026-10-18T01:46:26.817919187Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_JWTClaims_CorrectSubAndIssuer","Elapsed":0.14} +{"Time":"2026-10-18T01:46:26.817930657Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_ProfileScope"} +{"Time":"2026-10-18T01:46:26.81793825Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_ProfileScope","Output":"=== RUN TestTokenHandler_ScopeFiltering_ProfileScope\n"} +{"Time":"2026-10-18T01:46:26.851822453Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_ProfileScope","Output":"--- PASS: TestTokenHandler_ScopeFiltering_ProfileScope (0.03s)\n"} +{"Time":"2026-10-18T01:46:26.851899604Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_ProfileScope","Elapsed":0.03} +{"Time":"2026-10-18T01:46:26.851915393Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_AllScopes"} +{"Time":"2026-10-18T01:46:26.851924212Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_AllScopes","Output":"=== RUN TestTokenHandler_ScopeFiltering_AllScopes\n"} +{"Time":"2026-10-18T01:46:27.010908656Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_AllScopes","Output":"--- PASS: TestTokenHandler_ScopeFiltering_AllScopes (0.16s)\n"} +{"Time":"2026-10-18T01:46:27.010966971Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_ScopeFiltering_AllScopes","Elapsed":0.16} +{"Time":"2026-10-18T01:46:27.010978026Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TokenIssuedTelemetry"} +{"Time":"2026-10-18T01:46:27.011010154Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TokenIssuedTelemetry","Output":"=== RUN TestTokenHandler_TokenIssuedTelemetry\n"} +{"Time":"2026-10-18T01:46:27.043287505Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TokenIssuedTelemetry","Output":"--- PASS: TestTokenHandler_TokenIssuedTelemetry (0.03s)\n"} +{"Time":"2026-10-18T01:46:27.043344422Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TokenIssuedTelemetry","Elapsed":0.03} +{"Time":"2026-10-18T01:46:27.043355792Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeDeletedAfterUse"} +{"Time":"2026-10-18T01:46:27.04336224Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeDeletedAfterUse","Output":"=== RUN TestTokenHandler_CodeDeletedAfterUse\n"} +{"Time":"2026-10-18T01:46:27.08179692Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeDeletedAfterUse","Output":"--- PASS: TestTokenHandler_CodeDeletedAfterUse (0.04s)\n"} +{"Time":"2026-10-18T01:46:27.08185472Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_CodeDeletedAfterUse","Elapsed":0.04} +{"Time":"2026-10-18T01:46:27.081866632Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_PresentWhenTenantEngineReachable"} +{"Time":"2026-10-18T01:46:27.08187362Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_PresentWhenTenantEngineReachable","Output":"=== RUN TestTokenHandler_TenantRoles_PresentWhenTenantEngineReachable\n"} +{"Time":"2026-10-18T01:46:27.17504395Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_PresentWhenTenantEngineReachable","Output":"--- PASS: TestTokenHandler_TenantRoles_PresentWhenTenantEngineReachable (0.09s)\n"} +{"Time":"2026-10-18T01:46:27.175104375Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_PresentWhenTenantEngineReachable","Elapsed":0.09} +{"Time":"2026-10-18T01:46:27.175124195Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedButIssuanceSucceedsWhenUnreachable"} +{"Time":"2026-10-18T01:46:27.175131814Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedButIssuanceSucceedsWhenUnreachable","Output":"=== RUN TestTokenHandler_TenantRoles_OmittedButIssuanceSucceedsWhenUnreachable\n"} +{"Time":"2026-10-18T01:46:27.383211024Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedButIssuanceSucceedsWhenUnreachable","Output":"--- PASS: TestTokenHandler_TenantRoles_OmittedButIssuanceSucceedsWhenUnreachable (0.21s)\n"} +{"Time":"2026-10-18T01:46:27.383262693Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedButIssuanceSucceedsWhenUnreachable","Elapsed":0.21} +{"Time":"2026-10-18T01:46:27.383271701Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedWhenTenantEngineNotConfigured"} +{"Time":"2026-10-18T01:46:27.383276345Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedWhenTenantEngineNotConfigured","Output":"=== RUN TestTokenHandler_TenantRoles_OmittedWhenTenantEngineNotConfigured\n"} +{"Time":"2026-10-18T01:46:27.631018711Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedWhenTenantEngineNotConfigured","Output":"--- PASS: TestTokenHandler_TenantRoles_OmittedWhenTenantEngineNotConfigured (0.25s)\n"} +{"Time":"2026-10-18T01:46:27.631095656Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestTokenHandler_TenantRoles_OmittedWhenTenantEngineNotConfigured","Elapsed":0.25} +{"Time":"2026-10-18T01:46:27.631112265Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ValidToken_ReturnsClaims"} +{"Time":"2026-10-18T01:46:27.631135999Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ValidToken_ReturnsClaims","Output":"=== RUN TestUserinfoHandler_ValidToken_ReturnsClaims\n"} +{"Time":"2026-10-18T01:46:27.74542475Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ValidToken_ReturnsClaims","Output":"--- PASS: TestUserinfoHandler_ValidToken_ReturnsClaims (0.11s)\n"} +{"Time":"2026-10-18T01:46:27.745500775Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ValidToken_ReturnsClaims","Elapsed":0.11} +{"Time":"2026-10-18T01:46:27.745521974Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_CanonicalSubjectResolvesViaPreferredUsername"} +{"Time":"2026-10-18T01:46:27.74553091Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_CanonicalSubjectResolvesViaPreferredUsername","Output":"=== RUN TestUserinfoHandler_CanonicalSubjectResolvesViaPreferredUsername\n"} +{"Time":"2026-10-18T01:46:27.892976589Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_CanonicalSubjectResolvesViaPreferredUsername","Output":"--- PASS: TestUserinfoHandler_CanonicalSubjectResolvesViaPreferredUsername (0.15s)\n"} +{"Time":"2026-10-18T01:46:27.893093588Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_CanonicalSubjectResolvesViaPreferredUsername","Elapsed":0.15} +{"Time":"2026-10-18T01:46:27.893122317Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_SuspendedUserInvalidatesToken"} +{"Time":"2026-10-18T01:46:27.893133083Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_SuspendedUserInvalidatesToken","Output":"=== RUN TestUserinfoHandler_SuspendedUserInvalidatesToken\n"} +{"Time":"2026-10-18T01:46:27.984620153Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_SuspendedUserInvalidatesToken","Output":"--- PASS: TestUserinfoHandler_SuspendedUserInvalidatesToken (0.09s)\n"} +{"Time":"2026-10-18T01:46:27.984679883Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_SuspendedUserInvalidatesToken","Elapsed":0.09} +{"Time":"2026-10-18T01:46:27.984693995Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_MissingAuthorization_Returns401"} +{"Time":"2026-10-18T01:46:27.984700894Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_MissingAuthorization_Returns401","Output":"=== RUN TestUserinfoHandler_MissingAuthorization_Returns401\n"} +{"Time":"2026-10-18T01:46:28.208512464Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_MissingAuthorization_Returns401","Output":"--- PASS: TestUserinfoHandler_MissingAuthorization_Returns401 (0.22s)\n"} +{"Time":"2026-10-18T01:46:28.208577794Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_MissingAuthorization_Returns401","Elapsed":0.22} +{"Time":"2026-10-18T01:46:28.208592393Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ExpiredToken_Returns401"} +{"Time":"2026-10-18T01:46:28.208599237Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ExpiredToken_Returns401","Output":"=== RUN TestUserinfoHandler_ExpiredToken_Returns401\n"} +{"Time":"2026-10-18T01:46:28.381918308Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ExpiredToken_Returns401","Output":"--- PASS: TestUserinfoHandler_ExpiredToken_Returns401 (0.17s)\n"} +{"Time":"2026-10-18T01:46:28.3819805Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_ExpiredToken_Returns401","Elapsed":0.17} +{"Time":"2026-10-18T01:46:28.38199298Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_InvalidSignature_Returns401"} +{"Time":"2026-10-18T01:46:28.382017867Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_InvalidSignature_Returns401","Output":"=== RUN TestUserinfoHandler_InvalidSignature_Returns401\n"} +{"Time":"2026-10-18T01:46:28.511968876Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_InvalidSignature_Returns401","Output":"--- PASS: TestUserinfoHandler_InvalidSignature_Returns401 (0.13s)\n"} +{"Time":"2026-10-18T01:46:28.512025378Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_InvalidSignature_Returns401","Elapsed":0.13} +{"Time":"2026-10-18T01:46:28.512035875Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithEmailScope_EmailPresent"} +{"Time":"2026-10-18T01:46:28.512049427Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithEmailScope_EmailPresent","Output":"=== RUN TestUserinfoHandler_WithEmailScope_EmailPresent\n"} +{"Time":"2026-10-18T01:46:28.684541418Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithEmailScope_EmailPresent","Output":"--- PASS: TestUserinfoHandler_WithEmailScope_EmailPresent (0.17s)\n"} +{"Time":"2026-10-18T01:46:28.684603998Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithEmailScope_EmailPresent","Elapsed":0.17} +{"Time":"2026-10-18T01:46:28.684615729Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithoutEmailScope_EmailAbsent"} +{"Time":"2026-10-18T01:46:28.684623926Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithoutEmailScope_EmailAbsent","Output":"=== RUN TestUserinfoHandler_WithoutEmailScope_EmailAbsent\n"} +{"Time":"2026-10-18T01:46:28.791451878Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithoutEmailScope_EmailAbsent","Output":"--- PASS: TestUserinfoHandler_WithoutEmailScope_EmailAbsent (0.11s)\n"} +{"Time":"2026-10-18T01:46:28.79152737Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithoutEmailScope_EmailAbsent","Elapsed":0.11} +{"Time":"2026-10-18T01:46:28.79154315Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithProfileScope_UsernamePresent"} +{"Time":"2026-10-18T01:46:28.791564124Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithProfileScope_UsernamePresent","Output":"=== RUN TestUserinfoHandler_WithProfileScope_UsernamePresent\n"} +{"Time":"2026-10-18T01:46:28.832946534Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithProfileScope_UsernamePresent","Output":"--- PASS: TestUserinfoHandler_WithProfileScope_UsernamePresent (0.04s)\n"} +{"Time":"2026-10-18T01:46:28.833009456Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_WithProfileScope_UsernamePresent","Elapsed":0.04} +{"Time":"2026-10-18T01:46:28.833020701Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_EmitsTelemetry"} +{"Time":"2026-10-18T01:46:28.833027193Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_EmitsTelemetry","Output":"=== RUN TestUserinfoHandler_EmitsTelemetry\n"} +{"Time":"2026-10-18T01:46:28.936269561Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_EmitsTelemetry","Output":"--- PASS: TestUserinfoHandler_EmitsTelemetry (0.10s)\n"} +{"Time":"2026-10-18T01:46:28.936325089Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_EmitsTelemetry","Elapsed":0.1} +{"Time":"2026-10-18T01:46:28.936336956Z","Action":"run","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_UserNotFound_Returns401"} +{"Time":"2026-10-18T01:46:28.936344945Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_UserNotFound_Returns401","Output":"=== RUN TestUserinfoHandler_UserNotFound_Returns401\n"} +{"Time":"2026-10-18T01:46:29.03527725Z","Action":"output","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_UserNotFound_Returns401","Output":"--- PASS: TestUserinfoHandler_UserNotFound_Returns401 (0.10s)\n"} +{"Time":"2026-10-18T01:46:29.035337062Z","Action":"pass","Package":"keycape/internal/server/oidc","Test":"TestUserinfoHandler_UserNotFound_Returns401","Elapsed":0.1} +{"Time":"2026-10-18T01:46:29.035348956Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"PASS\n"} +{"Time":"2026-10-18T01:46:30.043464057Z","Action":"output","Package":"keycape/internal/server/oidc","Output":"ok \tkeycape/internal/server/oidc\t14.211s\n"} +{"Time":"2026-10-18T01:46:30.043522805Z","Action":"pass","Package":"keycape/internal/server/oidc","Elapsed":14.211} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-source-inventory.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-source-inventory.json new file mode 100644 index 0000000..b1c105f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/full-source-inventory.json @@ -0,0 +1,122 @@ +{ + "base": "d803e9282a47b49daf930fb905312fa5104c0ace", + "source": "/tmp/custodian-keycape-token-status-r1/source", + "sha256": { + "src/go.mod": "sha256:ab299ad4f94feb78e217e2228a4aa161b166460aa28df0c5009451ac1a337456", + "src/Makefile": "sha256:6cdd3aa6e1cccaf4e028278acafe3e6a8d28170de89ff3462984d4f6eb56bd96", + "src/go.sum": "sha256:b10ad54277fb2bd927d3af727fc4dbb9ebe91a038a75be0469d0253ca699226d", + "src/tests/negative/negative_test.go": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f", + "src/tests/conformance/external_test.go": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb", + "src/tests/migration/scenario_c_test.go": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80", + "src/tests/migration/fixtures_test.go": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de", + "src/tests/migration/scenario_b_test.go": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0", + "src/tests/profile/profile_test.go": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6", + "src/internal/domain/assurance_test.go": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755", + "src/internal/domain/conformance_test.go": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1", + "src/internal/domain/repository.go": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011", + "src/internal/domain/auth.go": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee", + "src/internal/domain/model.go": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029", + "src/internal/domain/assurance.go": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7", + "src/internal/domain/mfa.go": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87", + "src/internal/authclient/cli.go": "sha256:376a8e0d1d60825a8b3ef21d371e3fc353c0a34e461712a6899e264278edbe7f", + "src/internal/authclient/client.go": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5", + "src/internal/authclient/verify.go": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25", + "src/internal/authclient/client_test.go": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa", + "src/internal/authclient/verify_test.go": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc", + "src/internal/validator/report.go": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8", + "src/internal/validator/validator_test.go": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276", + "src/internal/validator/validator.go": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f", + "src/internal/config/account_test.go": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d", + "src/internal/config/validate.go": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405", + "src/internal/config/optional_mfa_test.go": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e", + "src/internal/config/config_test.go": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100", + "src/internal/config/token_status_test.go": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5", + "src/internal/config/config.go": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683", + "src/internal/errors/taxonomy.go": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c", + "src/internal/errors/taxonomy_test.go": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45", + "src/internal/jose/jose.go": "sha256:bcfc30d2175735f37e7ea6f58451e122fbdfe02c562805e5a018c863fb677fab", + "src/internal/jose/jose_test.go": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43", + "src/internal/server/policy/policy_test.go": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2", + "src/internal/server/policy/policy.go": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7", + "src/internal/server/errors/enforcement.go": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2", + "src/internal/server/errors/enforcement_test.go": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c", + "src/internal/server/telemetry/emitter.go": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0", + "src/internal/server/telemetry/emitter_test.go": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a", + "src/internal/server/telemetry/events.go": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167", + "src/internal/server/readiness/readiness_test.go": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6", + "src/internal/server/readiness/readiness.go": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073", + "src/internal/server/oidc/discovery_test.go": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0", + "src/internal/server/oidc/tenant_precondition_test.go": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f", + "src/internal/server/oidc/userinfo.go": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f", + "src/internal/server/oidc/account.go": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a", + "src/internal/server/oidc/policy_runtime_test.go": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a", + "src/internal/server/oidc/handoff_test.go": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02", + "src/internal/server/oidc/human_tenant_test.go": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5", + "src/internal/server/oidc/discovery.go": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5", + "src/internal/server/oidc/jwks.go": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60", + "src/internal/server/oidc/login_session.go": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f", + "src/internal/server/oidc/hardening_test.go": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e", + "src/internal/server/oidc/jwks_test.go": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa", + "src/internal/server/oidc/account_test.go": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763", + "src/internal/server/oidc/token_status_discovery_test.go": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031", + "src/internal/server/oidc/principal_type_guard_test.go": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40", + "src/internal/server/oidc/token_status_http.go": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97", + "src/internal/server/oidc/native_optional_test.go": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5", + "src/internal/server/oidc/authorize_test.go": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d", + "src/internal/server/oidc/session.go": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c", + "src/internal/server/oidc/logout.go": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893", + "src/internal/server/oidc/optional_mfa_test.go": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529", + "src/internal/server/oidc/token.go": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f", + "src/internal/server/oidc/token_status.go": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3", + "src/internal/server/oidc/userinfo_test.go": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80", + "src/internal/server/oidc/signin_diagnosis_test.go": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e", + "src/internal/server/oidc/policy_isolation_test.go": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077", + "src/internal/server/oidc/token_test.go": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33", + "src/internal/server/oidc/audience_test.go": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3", + "src/internal/server/oidc/token_status_transport_test.go": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347", + "src/internal/server/oidc/tenant_test.go": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713", + "src/internal/server/oidc/assurance_time_test.go": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a", + "src/internal/server/oidc/signin_diagnosis.go": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f", + "src/internal/server/oidc/authorize.go": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e", + "src/internal/server/oidc/handoff.go": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4", + "src/internal/server/oidc/token_status_internal_test.go": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53", + "src/internal/server/oidc/token_status_test.go": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90", + "src/internal/migration/lldapexport/exporter.go": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103", + "src/internal/migration/lldapexport/exporter_test.go": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2", + "src/internal/migration/tokeycloak/transformer_test.go": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b", + "src/internal/migration/tokeycloak/transformer.go": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa", + "src/internal/migration/toldap/generator.go": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46", + "src/internal/migration/toldap/generator_test.go": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc", + "src/internal/adapters/tenantengine/adapter.go": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3", + "src/internal/adapters/tenantengine/adapter_test.go": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0", + "src/internal/adapters/lldap/adapter.go": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef", + "src/internal/adapters/lldap/railiance_admin_test.go": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f", + "src/internal/adapters/lldap/adapter_test.go": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081", + "src/internal/adapters/lldap/config_internal_test.go": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68", + "src/internal/adapters/lldap/config.go": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b", + "src/internal/adapters/privacyidea/credential_file_test.go": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6", + "src/internal/adapters/privacyidea/read_scope_test.go": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3", + "src/internal/adapters/privacyidea/adapter.go": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904", + "src/internal/adapters/privacyidea/adapter_test.go": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349", + "src/internal/adapters/privacyidea/optional_mfa_test.go": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65", + "src/internal/adapters/privacyidea/enrollment_state_test.go": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377", + "src/internal/adapters/privacyidea/config.go": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021", + "src/internal/adapters/authelia/verify.go": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4", + "src/internal/adapters/authelia/adapter.go": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45", + "src/internal/adapters/authelia/adapter_test.go": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93", + "src/internal/adapters/authelia/issuer_probe.go": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb", + "src/internal/adapters/authelia/issuer_probe_test.go": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec", + "src/internal/adapters/authelia/config.go": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55", + "src/internal/adapters/authelia/verify_test.go": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511", + "src/cmd/keycape-to-keycloak/main.go": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7", + "src/cmd/lldap-to-ldap/main.go": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e", + "src/cmd/keycape/clients_test.go": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da", + "src/cmd/keycape/checkconfig.go": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2", + "src/cmd/keycape/main.go": "sha256:2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d", + "src/cmd/keycape/checkconfig_test.go": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66", + "src/cmd/validator/main.go": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f", + "src/cmd/lldap-export/main_test.go": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2", + "src/cmd/lldap-export/main.go": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75" + }, + "source_files": 115 +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-assignment.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-assignment.json new file mode 100644 index 0000000..a4f25df --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-assignment.json @@ -0,0 +1,38 @@ +{ + "schema": "repo-manager.identifier-assignment.v1", + "ok": true, + "executed": true, + "repo": "source", + "namespace": "helixforge", + "assignments": [ + { + "kind": "workplan", + "record_id": "KEY-WP-0042", + "uuid": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + }, + { + "kind": "task", + "record_id": "KEY-WP-0042-T01", + "uuid": "d8af8e77-f6e5-5d0d-8b44-794af3dee5cf", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + }, + { + "kind": "task", + "record_id": "KEY-WP-0042-T02", + "uuid": "2cade51f-d25a-5719-96b4-b57d50c1b97d", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + }, + { + "kind": "task", + "record_id": "KEY-WP-0042-T03", + "uuid": "a5539bb0-2501-5774-a7d8-acddfb0938ab", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + } + ], + "historical_invalid_identifiers": [], + "historical_identity_collisions": [], + "files_changed": [ + "workplans/KEY-WP-0042-online-access-token-status.md" + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preflight-refusal.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preflight-refusal.json new file mode 100644 index 0000000..c3b14da --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preflight-refusal.json @@ -0,0 +1,10 @@ +{ + "status": "refused", + "phase": "official repo-manager missing-identifier preview on isolated full owner archive", + "reason": "missing UUIDs on grandfathered or noncanonical records require an explicit identity decision: ADHOC-2026-09-27-T02 in workplans/ADHOC-2026-09-27.md", + "historic_source_sha256": "sha256:2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314", + "canonical_owner_changed": false, + "identifiers_assigned": false, + "new_plan": "proposed KEY-WP-0042 source draft; not registered owner work", + "next_action": "Reconcile the existing historical task binding through its owner identity path; do not invent a UUID or bypass ordinary publication" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preview.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preview.json new file mode 100644 index 0000000..3d6cd1c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/identifier-preview.json @@ -0,0 +1,38 @@ +{ + "schema": "repo-manager.identifier-assignment.v1", + "ok": true, + "executed": false, + "repo": "source", + "namespace": "helixforge", + "assignments": [ + { + "kind": "workplan", + "record_id": "KEY-WP-0042", + "uuid": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + }, + { + "kind": "task", + "record_id": "KEY-WP-0042-T01", + "uuid": "d8af8e77-f6e5-5d0d-8b44-794af3dee5cf", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + }, + { + "kind": "task", + "record_id": "KEY-WP-0042-T02", + "uuid": "2cade51f-d25a-5719-96b4-b57d50c1b97d", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + }, + { + "kind": "task", + "record_id": "KEY-WP-0042-T03", + "uuid": "a5539bb0-2501-5774-a7d8-acddfb0938ab", + "path": "workplans/KEY-WP-0042-online-access-token-status.md" + } + ], + "historical_invalid_identifiers": [], + "historical_identity_collisions": [], + "files_changed": [ + "workplans/KEY-WP-0042-online-access-token-status.md" + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-application.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-application.json new file mode 100644 index 0000000..eeea4d8 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-application.json @@ -0,0 +1,11 @@ +{ + "status": "source-applied", + "owner": "key-cape", + "base": "git:d803e9282a47b49daf930fb905312fa5104c0ace", + "files": 18, + "other_tracked_files_preserved": 271, + "existing_binding_restored": "7fa0bfc7-0772-57c7-8d6f-641615cab82a", + "new_identity_for_historical_task": false, + "independent_owner_assent": false, + "native_actions": false +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/before.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/before.json new file mode 100644 index 0000000..1e19280 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/before.json @@ -0,0 +1,1126 @@ +{ + "head": "d803e9282a47b49daf930fb905312fa5104c0ace", + "tracked": { + ".claude/rules/agents.md": { + "sha256": "sha256:55ad7da95751de483e42e08d3ccae7d6b04daf8ad5f7457047eff06778b05fa5", + "index": "100644 0e8a5d95b6d263fbaeae4261a7051112fb27ee18 0\t.claude/rules/agents.md" + }, + ".claude/rules/architecture.md": { + "sha256": "sha256:21314c8ac9329a2ab6f554807a7fe131a7417923b7e9faf5782ad25c70a4ff93", + "index": "100644 7c2a64580a2fb55d6586e154ee70d801430faa3e 0\t.claude/rules/architecture.md" + }, + ".claude/rules/credential-routing.md": { + "sha256": "sha256:8f2951b8808f7f3f63f0e2179948255fcf31b6f9c021b5053fcb6c5d0256a1e6", + "index": "100644 2ab2aee3c221b4c2f637786f66de431236b61bc5 0\t.claude/rules/credential-routing.md" + }, + ".claude/rules/first-session.md": { + "sha256": "sha256:605a48a56a3dfd84baa08bdf769cd012e4de566873712b7039d9390473d0271c", + "index": "100644 5b6b1876a29a4de2c3e6fd0f67c3ffbeb144c496 0\t.claude/rules/first-session.md" + }, + ".claude/rules/repo-boundary.md": { + "sha256": "sha256:3ab0772c1ea280c48a75f2d44a1d0494e6e124fd91145ad19e65ac48488ead22", + "index": "100644 b74d916907b49e9abed8a8bd8f4f3cd7aadf95ef 0\t.claude/rules/repo-boundary.md" + }, + ".claude/rules/repo-identity.md": { + "sha256": "sha256:5ae2098aa75878ddf966c8b0f7bfa532e40b320f776dd1e5e1c55bd04380ba03", + "index": "100644 c4b8fa16164b5a8b37fee603997121a5c9680aac 0\t.claude/rules/repo-identity.md" + }, + ".claude/rules/session-protocol.md": { + "sha256": "sha256:e61d95ebe2f1bc92594ab1fcc12e09c95f961a509ddc43ff9310b15fe7115a79", + "index": "100644 0c2d89e25d9b55e90982f7596f7336328a49c786 0\t.claude/rules/session-protocol.md" + }, + ".claude/rules/stack-and-commands.md": { + "sha256": "sha256:8debf28c7e28aeb321a0638a2e7f1dbdcb7c4013f06f5994caf5be240df902da", + "index": "100644 dc53ac69288ec906421a0e8e1e1083528c047702 0\t.claude/rules/stack-and-commands.md" + }, + ".claude/rules/workplan-convention.md": { + "sha256": "sha256:d5ef7350f78ad8091568dca79b072d615133256756b0584ffbc30c7320b063ac", + "index": "100644 f43cfff94d75b083b3ca10e7982654d71e75dfdf 0\t.claude/rules/workplan-convention.md" + }, + ".custodian-brief.md": { + "sha256": "sha256:96d84539b3a92786bff37fcc70161adec4648b9547287e52e6b817bc080a080a", + "index": "100644 d3b36c3bffbeb922769a2a788e35b4113fccaa3f 0\t.custodian-brief.md" + }, + ".forgejo/workflows/acceptance.yaml": { + "sha256": "sha256:1ac9f025edc3394f23c6cc46ee779abe81d70b9e798454ca50cabc070ee66ca9", + "index": "100644 b1bcdc2c729352cada7b654b59a07fe6850fc466 0\t.forgejo/workflows/acceptance.yaml" + }, + ".forgejo/workflows/image.yaml": { + "sha256": "sha256:5bb3910207295c830aa2a6f9bc65ab8cf9f264ea9d2687aacad88c39429f5644", + "index": "100644 0e16cc2ea75d37e1da788ca847c127fc04f5a98f 0\t.forgejo/workflows/image.yaml" + }, + ".gitea/workflows/image.yaml": { + "sha256": "sha256:849e4228aee5c2541ff14c87fbe875a629ee484d74e9cd00a827862f0aa3b958", + "index": "100644 e8491b77aad0b288977d3fb5eb8ca8db4a7725de 0\t.gitea/workflows/image.yaml" + }, + ".github/workflows/ci.yml": { + "sha256": "sha256:e2f9854245d0d62a0ab291b92eaf93c8787d036af42466bf2f8d05233ff7eaac", + "index": "100644 32a386e9115c36d1689d9ff595eab8d66f037286 0\t.github/workflows/ci.yml" + }, + ".gitignore": { + "sha256": "sha256:cfa7e0d0d9ab86e0246ba668147cdfe1c599ae34b1843780cdbccfaa3d0f7621", + "index": "100644 e705f7f1738a49dd9a78b5907d68d9429107c67c 0\t.gitignore" + }, + ".repo-classification.yaml": { + "sha256": "sha256:416fcc6bf409163146a88451a90a04980bb2530433e92579e14f9061a8a30207", + "index": "100644 4fc521fc9c4263b37c6978ea68651d1cb0c25faf 0\t.repo-classification.yaml" + }, + "AGENTS.md": { + "sha256": "sha256:77c8e1ac762e7c101cd09a3aa184993309ed057821bd53fb0d318979a45c9315", + "index": "100644 4a0df33e6635969b7158daec3e80bd28e5f0bbc1 0\tAGENTS.md" + }, + "CLAUDE.md": { + "sha256": "sha256:60ac8c6f039b1f285639ad7f1ebd815352b6fff0be3ec6401658c744aaca740e", + "index": "100644 e81c1b48c16dc3bf49af30e48b476107d916ee21 0\tCLAUDE.md" + }, + "Dockerfile": { + "sha256": "sha256:5ef0e8a932b999adba8deec6478ba21499edaff9351b863910de647bbfdcdef6", + "index": "100644 6adf9ce0636d7782fd231e418a0009deeee9868b 0\tDockerfile" + }, + "INTENT.md": { + "sha256": "sha256:b22cb440691bb399b064ee10ab1a03e1e07838da1051bf6235fbfe2cf820eb10", + "index": "100644 b2519df87f5b29b324187f97f7a0d92dff3fc874 0\tINTENT.md" + }, + "LICENSE": { + "sha256": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6", + "index": "100644 7e08e28d5f32bdea3a9e1c828ae9f1e21d9edae7 0\tLICENSE" + }, + "Makefile": { + "sha256": "sha256:184547fdd0fb7d62ce48c6f7cd904bca2cf62a7dddc78697faa18c6b2dabd59e", + "index": "100644 512fa11a68e460d410deff823e58b9608a342932 0\tMakefile" + }, + "README.md": { + "sha256": "sha256:953586cca96d01e9240448186b922c9b8ea03827a7f314731e9a9ead4f19d0d3", + "index": "100644 5435b16714d66247db4d99ab7782cf8cd21532d9 0\tREADME.md" + }, + "SCOPE.md": { + "sha256": "sha256:20fbadf3e33339bca4b541cdfa33a0de00095c968d51006dc4d0a46735cc834f", + "index": "100644 ab2522b2e0c0e6c008810e9929fc0b8d35121512 0\tSCOPE.md" + }, + "WORK-RECORDS.md": { + "sha256": "sha256:431842547bd82750f284f8c1c9f519892f1bb1c1a684b9b1e38adc6fdfdc299d", + "index": "100644 d86b29bbc480acfa6d12d5052039578d7d4e380c 0\tWORK-RECORDS.md" + }, + "bin/keycape": { + "sha256": "sha256:20bd7eefd64e027192bcc113029cd032b797332ab6150b9f1ff5d325d2bf5cb5", + "index": "100755 e7887dfbae490351f88d1f249e928f695f6a16d2 0\tbin/keycape" + }, + "bin/keycape-to-keycloak": { + "sha256": "sha256:7e5292c6ce338ea922aada2de13fe9011db98b8c9ca5d66533aef4bfe9818144", + "index": "100755 592504fd11f4c4fc821f3d6b0350b1285c38b0b0 0\tbin/keycape-to-keycloak" + }, + "bin/lldap-export": { + "sha256": "sha256:5320718452ca388788ba1316b437bc56a70f393fdde4079587eec284e027bcae", + "index": "100755 e49fe216aa04525d611c99ddf930639f98929ad0 0\tbin/lldap-export" + }, + "bin/lldap-to-ldap": { + "sha256": "sha256:480378861f090538507ffca0a83a752cea1c153549658e389a2ae725aa5061e8", + "index": "100755 9609697bd8e21396add3009893fcd8a11352eabd 0\tbin/lldap-to-ldap" + }, + "bin/validator": { + "sha256": "sha256:f548b0dc1746c8c492727a1c09fcb81cab7c8695b1608de3d421ab7d301061e5", + "index": "100755 b732e3274c05371e62768da3c3bdc3ac767329a0 0\tbin/validator" + }, + "capabilities/playbooks/key-cape.lightweight-sso.yaml": { + "sha256": "sha256:4ddc7b2ada4022ec3efa7693e9539de521a40140055e7066a78c67d9b0e2596b", + "index": "100644 b43901fe8a7c3ca61f509b5cb2eac8b2ef091064 0\tcapabilities/playbooks/key-cape.lightweight-sso.yaml" + }, + "capabilities/playbooks/key-cape.privacyidea-token-authority.yaml": { + "sha256": "sha256:0b0d8a75a97e9b8342496cf19d071f31e576eebad0343b95077b95d271db4d58", + "index": "100644 2872ef7727ac0fc2c3943296098fbea6f2a85f62 0\tcapabilities/playbooks/key-cape.privacyidea-token-authority.yaml" + }, + "config/dev-config.yaml": { + "sha256": "sha256:303dae14861a00403c10631457eb3ecb0f9615002053401b079d7d8f07ff4b50", + "index": "100644 bf8ea915b24d93be803ed1ef8c18c13a82976c9b 0\tconfig/dev-config.yaml" + }, + "config/service-clients.example.yaml": { + "sha256": "sha256:b7e17698a0946f387c481b7e27fe9ddea22cdd9db2e34e32a50457e02a34b18e", + "index": "100644 a4b13e5cffdc1cb683a202b90724bd86f565ccda 0\tconfig/service-clients.example.yaml" + }, + "docker-compose.dev.yml": { + "sha256": "sha256:dd30f791458c7c4169168ee272ac6b12378c828a6e757828c85fe55264c088d7", + "index": "100644 fa050d2a3722fe7372938ec2cb34b31244b7aa61 0\tdocker-compose.dev.yml" + }, + "docker-compose.scenario-b.yml": { + "sha256": "sha256:49fe67dab4113736cdea8d4a91491935e32212eaf73706933408957fe148efb3", + "index": "100644 a8976c28eb6c40af92e1daadcf168aaef8647ad5 0\tdocker-compose.scenario-b.yml" + }, + "docker-compose.scenario-c.yml": { + "sha256": "sha256:b46d665d0d740267a3cc6b297cbedce41215257181a51261458d8a0a715d8c59", + "index": "100644 5743fa39140e5bac1f2cad2479c5928470e76e1c 0\tdocker-compose.scenario-c.yml" + }, + "docs/adr/ADR-0001-choose-go-for-keycape.md": { + "sha256": "sha256:436b538b55a286914bf5039194e48634cdf1b1aa540c524862d27f63ef85bfed", + "index": "100644 52ab3e94c61c02ea0b13a0b8cf4e3f7d0f89eb40 0\tdocs/adr/ADR-0001-choose-go-for-keycape.md" + }, + "docs/approval-clients-deployment.patch.yaml": { + "sha256": "sha256:89bd4f0932f63b82de6363b31580c7f4b03b36981ae9d07658ebbdfb54149d22", + "index": "100644 f42bfba2462bbc739f409bb817ab85904236e3d1 0\tdocs/approval-clients-deployment.patch.yaml" + }, + "docs/approval-clients-rollout.md": { + "sha256": "sha256:b79f89997afe06538767a170c4ad6966e4573eff81bddd3ea12b79b27bf777e1", + "index": "100644 08b3e12ea62dd0da1354c59060434b88c9e7040f 0\tdocs/approval-clients-rollout.md" + }, + "docs/approval-engine-auth-contract.md": { + "sha256": "sha256:a8a86cd1b37bdb045a5c3dd601e033328f84e5691df1e2bb5a07723ccc226207", + "index": "100644 3c35fe6242d82b72a0a66b3da17f8e5dd078df2e 0\tdocs/approval-engine-auth-contract.md" + }, + "docs/approval-engine-provisioning-request.yaml": { + "sha256": "sha256:92d0e01f8a1a8db7ee59d4a37e4e4931fedeb98ac43d307f32721f5aa383b798", + "index": "100644 902c20cb3cfa6d4592947ac76df88a2fa5186649 0\tdocs/approval-engine-provisioning-request.yaml" + }, + "docs/authorization-code-bindings.md": { + "sha256": "sha256:fd8ce239444a05615b8e40b54ed9fef6454df74117d5fb17207b10c57d5f5921", + "index": "100644 f305058adf60bef56762c2588b5b5b2474f45430 0\tdocs/authorization-code-bindings.md" + }, + "docs/credential-renewal-release-2026-09-13.md": { + "sha256": "sha256:6694e801ae020fc975fe2cd922e3ee9bf93592f30905e81bb9816648d9b70325", + "index": "100644 de742b60f5872ad84195f33f2a5763952653fcd5 0\tdocs/credential-renewal-release-2026-09-13.md" + }, + "docs/enrollment-assurance-release-2026-09-13.md": { + "sha256": "sha256:3a776f2caab9312faf077fef1453c801a4abd926e20fc56865976215301234fa", + "index": "100644 c2061c6a760e810adccdc404e044cb79baa4fdcb 0\tdocs/enrollment-assurance-release-2026-09-13.md" + }, + "docs/evidence/2026-09-08-upstream-issuer-live-proof.json": { + "sha256": "sha256:25b08d6699e75a5aad134677bc6deb8b93e80017b9368455ca9fec22220b115e", + "index": "100644 85dab93ed1f2196810ead4f6b81a897fd06c33ff 0\tdocs/evidence/2026-09-08-upstream-issuer-live-proof.json" + }, + "docs/evidence/2026-09-09-keycape-verifier-admission.json": { + "sha256": "sha256:2cb6c59e06563013675990d8c40684dc73ccdb966429254501bb9f05ac0993c3", + "index": "100644 6ae6fc721df48426e93e505206a0df8e18ef2499 0\tdocs/evidence/2026-09-09-keycape-verifier-admission.json" + }, + "docs/evidence/2026-09-09-upstream-issuer-pin.json": { + "sha256": "sha256:1a43e9d8594c5a77114f5964f7f0ef63f8398fc7f0985eae524b82a9cbb45bbe", + "index": "100644 62fe341c90c514a92585641d14287f60076e8836 0\tdocs/evidence/2026-09-09-upstream-issuer-pin.json" + }, + "docs/evidence/2026-09-14-informed-decision-browser-registration.json": { + "sha256": "sha256:9c91dc3a53359ad6e8efb3d8f75ad87893c0d12dc14aec535ee19ba75b0941c6", + "index": "100644 cc87339baeae5406bd7ce76fe89bd0aaf95b366f 0\tdocs/evidence/2026-09-14-informed-decision-browser-registration.json" + }, + "docs/evidence/2026-09-24-fresh-login-and-account-switch.md": { + "sha256": "sha256:3c984ad2d502f114fcb6b837f7d5fff2d201338e6e62f34e403e77f53bce867d", + "index": "100644 200863c7e6b87f3045c1bffd2b951be1bdee5d84 0\tdocs/evidence/2026-09-24-fresh-login-and-account-switch.md" + }, + "docs/evidence/2026-10-07-secrets-engine-openbao-issuance.json": { + "sha256": "sha256:a405ef5289159c525695352ba71da7eca8e897b6f2dc7e5795f9b239361d8068", + "index": "100644 2278d9917b56923887b64b157b10de537739ee55 0\tdocs/evidence/2026-10-07-secrets-engine-openbao-issuance.json" + }, + "docs/evidence/2026-10-11-key39-next-source-inspection.json": { + "sha256": "sha256:d9bc54b645ea52dc3084f7a95f66456fa2206a6c0dcdd61207ede9af07054a79", + "index": "100644 d97906b5ee836fca2e52bbe42a462b02200f4fe5 0\tdocs/evidence/2026-10-11-key39-next-source-inspection.json" + }, + "docs/evidence/upstream-issuer-probe.json": { + "sha256": "sha256:3c276f34754d31a4819c4f65297d931b2659e8b29f7683c26459d574814eb8bf", + "index": "100644 88d98b835b63a016ede535cdf6416add36c17514 0\tdocs/evidence/upstream-issuer-probe.json" + }, + "docs/forgejo-oidc-client-request.yaml": { + "sha256": "sha256:7ba5bf23eb0a074a496cbb40c38d5c2ba688d5808dd558b2e74624ae8ccf44e5", + "index": "100644 04d0bccc32fa1e0d5739f22b5d71a638e345e386 0\tdocs/forgejo-oidc-client-request.yaml" + }, + "docs/native-authentication.md": { + "sha256": "sha256:378a5fa8174580bde811b716ec8e987339013b9f8d7725cb398fa3b9f8d95655", + "index": "100644 e714df21c4113ac853f2fbbf9309c06ea1b05863 0\tdocs/native-authentication.md" + }, + "docs/offline-checker-owner-review-2026-10-16.json": { + "sha256": "sha256:aa9083e77ea08d166e8cec7c5d73362c19058567c5e80a576766af3745958dc0", + "index": "100644 9f3c00bf7dcef2b3fa40a1c0fd99a7c03cb8facf 0\tdocs/offline-checker-owner-review-2026-10-16.json" + }, + "docs/offline-checker-owner-review-2026-10-16.md": { + "sha256": "sha256:75ed6866b29cbb9f2c5c2a395da2afa25d021f248587c7055bd721e2c55b8685", + "index": "100644 be834c5854e2d1ac3f2747fb45dd80953508ff45 0\tdocs/offline-checker-owner-review-2026-10-16.md" + }, + "docs/oidc-browser-relying-party-type-review.md": { + "sha256": "sha256:0bc277ff71eec01dc8f46a952b7eb152cc1727d0b138f384050ac15b654cd72a", + "index": "100644 259f7e10fb0a6aaa071aac342906cde85c056609 0\tdocs/oidc-browser-relying-party-type-review.md" + }, + "docs/openbao-negative-probe-clients.yaml": { + "sha256": "sha256:23c3d49e713de76d75916aede5bdd109d8918169a3078e4e776f0478064dc778", + "index": "100644 713036618418c02f612071f41c4864b9b67c900a 0\tdocs/openbao-negative-probe-clients.yaml" + }, + "docs/openbao-negative-probe-session-proposal.md": { + "sha256": "sha256:82ab5678426ae78d44464b1293c76bef10d1fedda0b8cea6e0cc2514b89c8684", + "index": "100644 fe9a9945d837fec98dc15452c1e7935b6a4fd0f1 0\tdocs/openbao-negative-probe-session-proposal.md" + }, + "docs/openbao-negative-token-producer-decision.json": { + "sha256": "sha256:7b3e4a53ac4af33c780f16c3f1aa9cde0154040ad2747a8d361dd79bbb7f435c", + "index": "100644 d3b806af1dd024bfc0f518d30ef26e1b715be361 0\tdocs/openbao-negative-token-producer-decision.json" + }, + "docs/openbao-negative-token-producer-decision.md": { + "sha256": "sha256:38e4c9660ca57a1fb6e5ff04f214af61dd1a58abf564a5ed22216d909e27fc73", + "index": "100644 e44cbcb98a7bb4efd727a026e70db45f5195fa30 0\tdocs/openbao-negative-token-producer-decision.md" + }, + "docs/openbao-service-auth-contract.md": { + "sha256": "sha256:9b815eff44ebe898b93da5f4a5eeb927fe23d9490397386f3bca3682ce5c81dc", + "index": "100644 3416a5943e1b535870d16841c10a89fdb65521c8 0\tdocs/openbao-service-auth-contract.md" + }, + "docs/operations.md": { + "sha256": "sha256:e68ec32073b7731214308ccd2fceb522d934b00f1d76e6d438e2870030497cfc", + "index": "100644 3092a16568b37610407e4d613b01a18bead5e960 0\tdocs/operations.md" + }, + "docs/optional-mfa.md": { + "sha256": "sha256:e1725c3e453b00ff74c7a07c2392ed7a2723fa9a159bd174d52a7fa4f1697c0b", + "index": "100644 f95f57aaa5cfd6203ea5d1b0ce99369ec32c40cc 0\tdocs/optional-mfa.md" + }, + "docs/proposals/2026-10-11-key39-founder-source-design-decision.json": { + "sha256": "sha256:d2f6e2fc733d8092a27a1dafb2f77601cbff398c5606b12bb053b4cd7bcd4091", + "index": "100644 2be7eb82c4ee1ec9a4b9227d2163b063063e40f7 0\tdocs/proposals/2026-10-11-key39-founder-source-design-decision.json" + }, + "docs/proposals/2026-10-11-key39-founder-source-design-decision.md": { + "sha256": "sha256:1816e48b44eb874e4c97db82ee56ed27c23083ddcfc322d9de9876132e802688", + "index": "100644 ad23cbde3d063f40e85948d0b37ec99847f24840 0\tdocs/proposals/2026-10-11-key39-founder-source-design-decision.md" + }, + "docs/proposals/2026-10-11-key39-next-execution-prerequisites.json": { + "sha256": "sha256:78288f6991910715b7ee8fdb2a32654418dd9becd5fccd254bab8d150420cfd3", + "index": "100644 09fe2e81cdda1372fc0ed7ec2e3103d303d7211d 0\tdocs/proposals/2026-10-11-key39-next-execution-prerequisites.json" + }, + "docs/qonto-runtime-identity-contract.md": { + "sha256": "sha256:24bc64dad4b6a6c01e69c2daeb35c62bd8e4539b18d1af329d4f647fc8b124cb", + "index": "100644 714a84c4a5d9e9258110b703297763537bbd07ec 0\tdocs/qonto-runtime-identity-contract.md" + }, + "docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json": { + "sha256": "sha256:776e5a522be3a73a00cf690c68fe084d12d5cd04117666f2dab9031d07537199", + "index": "100644 315a7210651a2777eeca2a5f502cfa5b91207600 0\tdocs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json" + }, + "docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.sources.json": { + "sha256": "sha256:a272cc4af0979a5e47de36515aa5de256998a7006a038a0a00182b79bcc7227b", + "index": "100644 6c462037bb51a833451f37ddcca6a347656f354d 0\tdocs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.sources.json" + }, + "docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json": { + "sha256": "sha256:6e90cbe246658325b01b672162da5b336f65bb769b7608ef578473a409278a13", + "index": "100644 af94cc71214d638d2e33cdb455b29c9ef5cfb1f4 0\tdocs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json" + }, + "docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.sources.json": { + "sha256": "sha256:e448fbf4c8d65886e3f790b91ac2856ea50ae866428086d2ae422073b449ecfa", + "index": "100644 1a1ebdddca7e3e1c419ef293466174644dfdec1b 0\tdocs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.sources.json" + }, + "docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json": { + "sha256": "sha256:10f74210b02c9c47370a0da223719a7f2dbc5c181319c7ad8f4de282c6efd03e", + "index": "100644 9ad2ee43cf5c66a66ff0472f4c9f40d0e550283a 0\tdocs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json" + }, + "docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.sources.json": { + "sha256": "sha256:6ba36436559cdc0da035faaf3718f5736a68633a731f103a7084feba5943546f", + "index": "100644 567ee0f3aa1b14f8633e874a35530eb76b8c93c4 0\tdocs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.sources.json" + }, + "docs/reviews/2026-10-11-key39-v7-input-keycape-source-review.json": { + "sha256": "sha256:73d8ee75c6201f650d32658d0731e2e91ae40cf53e1693b0f5fb029c42a75723", + "index": "100644 f0aa85ec5484751c8e490e2c60a77a600e21a1e1 0\tdocs/reviews/2026-10-11-key39-v7-input-keycape-source-review.json" + }, + "docs/reviews/2026-10-11-key39-v7-input-keycape-source-review.sources.json": { + "sha256": "sha256:4d405fe6547e29a5cc849d698e4d8ea04dede70a25595142f025efbe96e371d7", + "index": "100644 f4d4d9013656d1039585e32e3a9777eab096fa71 0\tdocs/reviews/2026-10-11-key39-v7-input-keycape-source-review.sources.json" + }, + "docs/reviews/2026-10-11-key39-v7-input-keycape-work-record.json": { + "sha256": "sha256:557269082b81bf82bb0c35e8f651342def8aa79a7794d970cd973fcf583fdc6b", + "index": "100644 2b5ccdb5341c43030ff87a62d418e2322d4e75af 0\tdocs/reviews/2026-10-11-key39-v7-input-keycape-work-record.json" + }, + "docs/reviews/2026-10-11-key39-v8-input-keycape-source-ack.json": { + "sha256": "sha256:100430d7c31050b1c68fa6c9b6b2b37e4f914f36fd4591b9f4d9ed62544457c1", + "index": "100644 7e4d3ec75f7671b2327f7eccfc51d1d902db275f 0\tdocs/reviews/2026-10-11-key39-v8-input-keycape-source-ack.json" + }, + "docs/reviews/2026-10-11-key39-v8-input-keycape-source-ack.sources.json": { + "sha256": "sha256:0c601702c4ade690c33368784944a5239c4aadee6d47b1fe3768267bcab23369", + "index": "100644 1d9a58cd94ceba2d4727060eaf13cc0442240352 0\tdocs/reviews/2026-10-11-key39-v8-input-keycape-source-ack.sources.json" + }, + "docs/reviews/2026-10-11-key39-v8-input-keycape-work-record.json": { + "sha256": "sha256:ed9f94f2c58f8210461385e41e02707da92539f1594070a585bfe5a6db6b9488", + "index": "100644 69a2ab4de8cbe2987a2be322648aa90ace9e26d6 0\tdocs/reviews/2026-10-11-key39-v8-input-keycape-work-record.json" + }, + "docs/reviews/2026-10-11-key39-v9-v4-keycape-source-ack.json": { + "sha256": "sha256:4108d3afde1756c3889e6092878afd99a1193e59971ffe04d3845bbe21a42c7e", + "index": "100644 553e90a108f111a9c42b88e16e1f38ff38af3231 0\tdocs/reviews/2026-10-11-key39-v9-v4-keycape-source-ack.json" + }, + "docs/reviews/2026-10-11-key39-v9-v4-keycape-source-ack.sources.json": { + "sha256": "sha256:2a7a587264ceeb902040417d0bc5c329af461590d8bc99edf818ec5699f0a5b0", + "index": "100644 524f47af20bae995988f59285c5f29283261e007 0\tdocs/reviews/2026-10-11-key39-v9-v4-keycape-source-ack.sources.json" + }, + "docs/reviews/2026-10-11-key39-v9-v4-keycape-work-record.json": { + "sha256": "sha256:b5074fdc22eb8f62ae035254bfb8311272cf8a7ec6ed14b60eebd14f5ef4046c", + "index": "100644 acf3e488fa9af74216f669e3f84bdf5d22e3963b 0\tdocs/reviews/2026-10-11-key39-v9-v4-keycape-work-record.json" + }, + "docs/secrets-engine-openbao-owner-packet.md": { + "sha256": "sha256:0bc704a3c18cd475c151e6e1d208ff5f9e1a45ce565fe3f4f009d6a3fccc5860", + "index": "100644 2b7e30ef9b82ebbf22ad589d31d0fccb74b33c95 0\tdocs/secrets-engine-openbao-owner-packet.md" + }, + "docs/sitting-requester-registration.json": { + "sha256": "sha256:0d71459be790c229a8d8e62b5cb94a3d4e7ef9c16cf3e82a2dba9804123e416e", + "index": "100644 ce5f53365f2ae3b14cd53f4b43c248f77c5a0223 0\tdocs/sitting-requester-registration.json" + }, + "docs/t03-requester-registration.json": { + "sha256": "sha256:189c0b8341c98d0c0eb82670b8a4655692ba0f93a56ec03dc03c3ec264e29f6e", + "index": "100644 d45478c9ef6363a735fd3605417e9275c18a2a94 0\tdocs/t03-requester-registration.json" + }, + "docs/t03-requester-registration.md": { + "sha256": "sha256:578b56b18359c9c59e9d67692e4f11fbfe5fd6fa10e822528a4340759e584247", + "index": "100644 32efcf0a5afc5c660bd754eb494020c32f609bdd 0\tdocs/t03-requester-registration.md" + }, + "docs/tenant-claim-contract.md": { + "sha256": "sha256:a7ce4f59c39f6b953a0ebb0592329e20e7830e64d7297abd05df72b758a23483", + "index": "100644 9ab277b85b550ac52ccf98bfda0a0dd7c744e171 0\tdocs/tenant-claim-contract.md" + }, + "docs/upstream-issuer-proof.md": { + "sha256": "sha256:8076cb5efff621b4b4c4d1d2919caec1affecc4c14de23f37f6143b734077811", + "index": "100644 4e7d26b6a1cbcacf6498aed0e33da8964160c39e 0\tdocs/upstream-issuer-proof.md" + }, + "history/2026-09-05-011726-scope-intent-assessment.md": { + "sha256": "sha256:893e9586e83b0598aeba4b01f69b0dcbd15de90a7b102663015a1576c4d7508d", + "index": "100644 4f305e21acfa7ad7f44b70542ba8f627b5ef9362 0\thistory/2026-09-05-011726-scope-intent-assessment.md" + }, + "history/2026-09-09-scope-reassessment.md": { + "sha256": "sha256:25383fac79195d715b8b875363813750125be1b860fe328b2f666786567d9136", + "index": "100644 394eed11f5afb16de7eda7b09f99cf59dbbbacce 0\thistory/2026-09-09-scope-reassessment.md" + }, + "history/2026-09-28-railiance-admin-role.md": { + "sha256": "sha256:a43ac13b0c13d25a576803d29732f41f68653869744db415a70120515ce8f2ee", + "index": "100644 2c9e75f72d9983b1b2980d79b12380564465e640 0\thistory/2026-09-28-railiance-admin-role.md" + }, + "history/KEY-WP-0011-live-secret-exposure-recovery.md": { + "sha256": "sha256:788c6b7d2fe0d36eee870d1d5b0a28da2a2d53916c53835e458ac55258fc8a55", + "index": "100644 77233c20c2230459b2d78d9d28578e9472e568ce 0\thistory/KEY-WP-0011-live-secret-exposure-recovery.md" + }, + "history/KEY-WP-0011-rotated-credentials-2026-08-23-final.tar.age": { + "sha256": "sha256:71478bd7f493dd64e12fd123af386294e66f975b2072cf24d8468086a350b2f7", + "index": "100644 8eef81bac925dacfa8408bdfb4e5a2d672ba903e 0\thistory/KEY-WP-0011-rotated-credentials-2026-08-23-final.tar.age" + }, + "intakes/intakes.md": { + "sha256": "sha256:780d2792c26dc55293bb12f6af341949eda0b5fab128574fd7115d259cd0c601", + "index": "100644 845c3025f781d2296f40b4ec93c43935a480295d 0\tintakes/intakes.md" + }, + "registry/README.md": { + "sha256": "sha256:921c03c506d3e1c12b4796c18ac1cb74010e4f0bbada679f68edf55bda2518bb", + "index": "100644 569abe99c95e163d39048f4b02ccb47165751c7e 0\tregistry/README.md" + }, + "registry/capabilities/.gitkeep": { + "sha256": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "index": "100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0\tregistry/capabilities/.gitkeep" + }, + "registry/capabilities/capability.iam.key-cape.md": { + "sha256": "sha256:c987ddd0155c248e35409878e9b2dc36ffd18bbd1b82fce8a90157fc4224a915", + "index": "100644 59cc40e2ccad5b2ce9111366efbc9e48f8e18e70 0\tregistry/capabilities/capability.iam.key-cape.md" + }, + "registry/indexes/capabilities.yaml": { + "sha256": "sha256:901d15c15cacd844cf19a1a7b74c890ce5e0b00a3372a9e10bd7114f328596c8", + "index": "100644 0718f5218c9f4b4ef84c6d9aacd4b61440098405 0\tregistry/indexes/capabilities.yaml" + }, + "scripts/bootstrap-dev.sh": { + "sha256": "sha256:c05ddd72d2309c4d7900a9767eecc17c29e5ecd3c51c897438a8ffc9f5f6aa97", + "index": "100755 bf05bb3db90d62b77a1104e48d8e31045ccf0d8e 0\tscripts/bootstrap-dev.sh" + }, + "scripts/collect-upstream-issuer-proof.py": { + "sha256": "sha256:e810aea8d51be122f5fa7ce0e21128c0c76672a0cb6a059afa0bfdb68f09f5dd", + "index": "100644 29627c2056fbcf511bce069db0a8803c797295ff 0\tscripts/collect-upstream-issuer-proof.py" + }, + "scripts/factor_recovery.py": { + "sha256": "sha256:247be2b1c25007539208a02fb14c4b64d1f71df4be8dfae708ba1dc4d226449a", + "index": "100644 10832318492c4b34230400e458209777d58b08a1 0\tscripts/factor_recovery.py" + }, + "scripts/keycape_onboarding_guard.py": { + "sha256": "sha256:9465e5e0b98f7986c2bfdc2a72be5882677c2acddf2f7a10c4e7e14cf6a150c0", + "index": "100644 2bc28a205ffbd14c4f077c61957f5e3c0af284fb 0\tscripts/keycape_onboarding_guard.py" + }, + "scripts/openbao_negative_private_handoff.py": { + "sha256": "sha256:faf63a3e9504dcbb46847a114275f83d508521e095410a47f92f02b86ba82b1b", + "index": "100644 e3a875e114d176431b517667c0c096ac4a974ad2 0\tscripts/openbao_negative_private_handoff.py" + }, + "scripts/openbao_negative_private_launcher.py": { + "sha256": "sha256:46e57504b3185cc13f1656901a0ca02cc3887527cb09566e270f57b76099ab9c", + "index": "100644 4eae60ab0d0766a390743ed33d665ea852bb285e 0\tscripts/openbao_negative_private_launcher.py" + }, + "scripts/openbao_negative_probe.py": { + "sha256": "sha256:bb9610babf44a959342bac700bb5712e97f400b44c4d0659d21df4426e616e19", + "index": "100644 cb864ae0b2b46650a5e20ae90e9465701c362011 0\tscripts/openbao_negative_probe.py" + }, + "scripts/provider-browser-fixture.py": { + "sha256": "sha256:5d2278cd6c6871fe9370f2eebe0faeb2630abdeac85e8745cf1f81964b810a07", + "index": "100644 477857655b8316659d7a3a2afcbe330e497d3851 0\tscripts/provider-browser-fixture.py" + }, + "scripts/provider-onboarding-contract.py": { + "sha256": "sha256:7dcc18dbe739ebaebbf0895f8324e7f3a6d978a9340630c18eedd7f946565455", + "index": "100644 5ac86bfec073a054b712fa7a82fd44066f203a29 0\tscripts/provider-onboarding-contract.py" + }, + "scripts/provider_browser.mjs": { + "sha256": "sha256:d892b77aec406dc1d581281acdf12f9ccc55bd176e9ad7a23378803f6cb759a0", + "index": "100644 2abdf8d67ab9a61f93ab3ef90d36942ee7659702 0\tscripts/provider_browser.mjs" + }, + "scripts/provider_browser_acceptance.py": { + "sha256": "sha256:33aa5e6b5834e4d8b212da028b73b62909bf0310a4b6b1ffea9c990fae4e78ed", + "index": "100644 bc9f59737a06c59e7746aecd00df8112a9c830d1 0\tscripts/provider_browser_acceptance.py" + }, + "scripts/recovery_service.py": { + "sha256": "sha256:90746f479e4a4904d41bb80b0a583809c9d30f07a97f034c84f46c1d9d0abccd", + "index": "100644 ca0bb2a3554c569d3923c48bebe0850a1f4254ff 0\tscripts/recovery_service.py" + }, + "scripts/render-upstream-issuer-probe.py": { + "sha256": "sha256:4f9a79b8332a9c830181c9aacf28e76801d80ffb9669a0e822c1ae86d9a41b20", + "index": "100644 0f2dca8e890f18bf5430596a1c722bdc4bcb4d0c 0\tscripts/render-upstream-issuer-probe.py" + }, + "scripts/test-scenario-b.sh": { + "sha256": "sha256:7186980e174ee10c9c35fa2313f78390f56dedf6cf35b2738ad443d763ecb9d9", + "index": "100755 48a5832ed965624685eeeb9cad396b8e6a854426 0\tscripts/test-scenario-b.sh" + }, + "scripts/test-scenario-c.sh": { + "sha256": "sha256:d2ddda2fba72d50fc474a61d714c8fae0b8f1440ab67ea65841b8a8a9b99a7b8", + "index": "100755 e333ec26464ade014e45d7a13b153d0a6f29b0a1 0\tscripts/test-scenario-c.sh" + }, + "scripts/test_factor_recovery.py": { + "sha256": "sha256:49a69b6f60e31545929988e035e21d4d18984a6a130ac49e4980bc5e35aa07aa", + "index": "100644 25d961f46258329f2290811101388c293e16b025 0\tscripts/test_factor_recovery.py" + }, + "scripts/test_openbao_negative_private_handoff.py": { + "sha256": "sha256:e4eb600718e7d93b0e801ac3ab1427af9895a6e5d877c1134369e049e0849797", + "index": "100644 c17061849c2a8f9b6c614e69888502607c612674 0\tscripts/test_openbao_negative_private_handoff.py" + }, + "scripts/test_openbao_negative_private_launcher.py": { + "sha256": "sha256:243426a85075b3447adeb2ca320a13711409744f71837a06699de6ad5b04ee9d", + "index": "100644 e1d98a393d5276d64d689c3b961790219e939f77 0\tscripts/test_openbao_negative_private_launcher.py" + }, + "scripts/test_openbao_negative_private_preparation.py": { + "sha256": "sha256:8e4dd327c4a339a0151ecba57db5987965b5d536ca18123fb7c74a047a241548", + "index": "100644 8344b22b4d62deb60ccafafa7acca336d226b0a8 0\tscripts/test_openbao_negative_private_preparation.py" + }, + "scripts/test_openbao_negative_probe.py": { + "sha256": "sha256:08d5598357c4db72672b2c4dc21ef09711ac4f9873fb29f8a6949d34afb8d85c", + "index": "100644 242d4502ab1f2bda9886ec0936c647019c426696 0\tscripts/test_openbao_negative_probe.py" + }, + "scripts/test_recovery_service.py": { + "sha256": "sha256:7ba236fa95c55a36eeece50e04440999b03318bf65644a8f9fe5a63877796752", + "index": "100644 6cdb5acd55440ed089e2842f5c762627363defa1 0\tscripts/test_recovery_service.py" + }, + "scripts/test_render_upstream_issuer_probe.py": { + "sha256": "sha256:fd407b5c8ef4753bfb0878aef044fdce46463b30623c87133e480823655bcf70", + "index": "100644 ab6f45297f354d006c4e24beb698bafe8a93a31b 0\tscripts/test_render_upstream_issuer_probe.py" + }, + "spec/canonical-model.yaml": { + "sha256": "sha256:ea4516d17235ee2cf9e8df2be23e61f5f81aa1fcbb661cce9c1dfc4158acebf1", + "index": "100644 fca9b9d450cdee91d383ff8a67c738774c5aca4c 0\tspec/canonical-model.yaml" + }, + "spec/ldap-schema.yaml": { + "sha256": "sha256:65af5ae7f697803cb29fc4fcc8988d899612591f72b04d844e0baccfb8b1e4b7", + "index": "100644 41cc35711da626fd1986f0da439b230235cac4df 0\tspec/ldap-schema.yaml" + }, + "src/Makefile": { + "sha256": "sha256:6cdd3aa6e1cccaf4e028278acafe3e6a8d28170de89ff3462984d4f6eb56bd96", + "index": "100644 083df0b3e537a5ebb09b9a454f25c68d650fdac8 0\tsrc/Makefile" + }, + "src/cmd/keycape-to-keycloak/main.go": { + "sha256": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7", + "index": "100644 faca3748339a924aff723dcb3ed10ec8d737248f 0\tsrc/cmd/keycape-to-keycloak/main.go" + }, + "src/cmd/keycape/checkconfig.go": { + "sha256": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2", + "index": "100644 5edcfd7d4fe8f6af021676e8f0f8a0b258831c37 0\tsrc/cmd/keycape/checkconfig.go" + }, + "src/cmd/keycape/checkconfig_test.go": { + "sha256": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66", + "index": "100644 93e4c44654ca28fb16027748c9c24d91d5ced4bd 0\tsrc/cmd/keycape/checkconfig_test.go" + }, + "src/cmd/keycape/clients_test.go": { + "sha256": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da", + "index": "100644 c2b60259d95831503e35a0c67fbe6e3d0c4169f3 0\tsrc/cmd/keycape/clients_test.go" + }, + "src/cmd/keycape/main.go": { + "sha256": "sha256:ff1ee4f5d2eb86a1e4184374d5244e24704d9d5fd780e173062af07112344afd", + "index": "100644 16de34c66062718b91821d0b836e258c82783401 0\tsrc/cmd/keycape/main.go" + }, + "src/cmd/lldap-export/main.go": { + "sha256": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75", + "index": "100644 daac6e986c48fcf2de9243ef78a40e0b88b9de4a 0\tsrc/cmd/lldap-export/main.go" + }, + "src/cmd/lldap-export/main_test.go": { + "sha256": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2", + "index": "100644 5aa9a2bb1472931b7b9c798fbebfee45fe316f9b 0\tsrc/cmd/lldap-export/main_test.go" + }, + "src/cmd/lldap-to-ldap/main.go": { + "sha256": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e", + "index": "100644 c98b19b31f2c2be0124c88f001b33048a7221968 0\tsrc/cmd/lldap-to-ldap/main.go" + }, + "src/cmd/validator/main.go": { + "sha256": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f", + "index": "100644 411272078c48cc52abce1007c0939528c66581fa 0\tsrc/cmd/validator/main.go" + }, + "src/go.mod": { + "sha256": "sha256:ab299ad4f94feb78e217e2228a4aa161b166460aa28df0c5009451ac1a337456", + "index": "100644 992c33f91d6b7f1244634cf583072f4bfbf73759 0\tsrc/go.mod" + }, + "src/go.sum": { + "sha256": "sha256:b10ad54277fb2bd927d3af727fc4dbb9ebe91a038a75be0469d0253ca699226d", + "index": "100644 d1f81c7aef0bb7c7892e9dd2131653661659bd0b 0\tsrc/go.sum" + }, + "src/internal/adapters/authelia/adapter.go": { + "sha256": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45", + "index": "100644 b78811acf78adad92d9a412c13cbb7aa86866468 0\tsrc/internal/adapters/authelia/adapter.go" + }, + "src/internal/adapters/authelia/adapter_test.go": { + "sha256": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93", + "index": "100644 81ac6205d8217509e6130b159c77e94291ca55a5 0\tsrc/internal/adapters/authelia/adapter_test.go" + }, + "src/internal/adapters/authelia/config.go": { + "sha256": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55", + "index": "100644 810f5b9387721058fb629a305d3aaf5254c65a12 0\tsrc/internal/adapters/authelia/config.go" + }, + "src/internal/adapters/authelia/issuer_probe.go": { + "sha256": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb", + "index": "100644 082708a178b818d83632bfe31c77696c03530c60 0\tsrc/internal/adapters/authelia/issuer_probe.go" + }, + "src/internal/adapters/authelia/issuer_probe_test.go": { + "sha256": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec", + "index": "100644 7943c3f59d5c59aba5adaff9dab1af4d4055d635 0\tsrc/internal/adapters/authelia/issuer_probe_test.go" + }, + "src/internal/adapters/authelia/verify.go": { + "sha256": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4", + "index": "100644 7c85d3b9ea7d7843092b24d812807fb6b48755dd 0\tsrc/internal/adapters/authelia/verify.go" + }, + "src/internal/adapters/authelia/verify_test.go": { + "sha256": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511", + "index": "100644 d9003680dc01f3d709d938ff8843f0a96b8a69f6 0\tsrc/internal/adapters/authelia/verify_test.go" + }, + "src/internal/adapters/lldap/adapter.go": { + "sha256": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef", + "index": "100644 e15cecd7c7ee83383745a367502bf6ab3d1280fe 0\tsrc/internal/adapters/lldap/adapter.go" + }, + "src/internal/adapters/lldap/adapter_test.go": { + "sha256": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081", + "index": "100644 e153314a0fc06e33ce8b957b66cc1838ee93a180 0\tsrc/internal/adapters/lldap/adapter_test.go" + }, + "src/internal/adapters/lldap/config.go": { + "sha256": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b", + "index": "100644 fce760a4e97c94e1e542a4d2f0895a20c709fed9 0\tsrc/internal/adapters/lldap/config.go" + }, + "src/internal/adapters/lldap/config_internal_test.go": { + "sha256": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68", + "index": "100644 d9e10ff898c19e85160e8b047f79a1d9daa797c7 0\tsrc/internal/adapters/lldap/config_internal_test.go" + }, + "src/internal/adapters/lldap/railiance_admin_test.go": { + "sha256": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f", + "index": "100644 d76e938ebfc2f676d5dd0a0639707d1e7f4ebc08 0\tsrc/internal/adapters/lldap/railiance_admin_test.go" + }, + "src/internal/adapters/privacyidea/adapter.go": { + "sha256": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904", + "index": "100644 35e65335a00ead963c3cd8e95e1b94a5b34e6a5a 0\tsrc/internal/adapters/privacyidea/adapter.go" + }, + "src/internal/adapters/privacyidea/adapter_test.go": { + "sha256": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349", + "index": "100644 286562d56a3f263df68f34eb35bf68b6581688da 0\tsrc/internal/adapters/privacyidea/adapter_test.go" + }, + "src/internal/adapters/privacyidea/config.go": { + "sha256": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021", + "index": "100644 b34c9f3d19109a7a0acc309c27639ce913f1e751 0\tsrc/internal/adapters/privacyidea/config.go" + }, + "src/internal/adapters/privacyidea/credential_file_test.go": { + "sha256": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6", + "index": "100644 017315272e6baa1151d4e73912b19c3bb8ca377b 0\tsrc/internal/adapters/privacyidea/credential_file_test.go" + }, + "src/internal/adapters/privacyidea/enrollment_state_test.go": { + "sha256": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377", + "index": "100644 6f3b883c0c7eced42bfdbe45713b1be5dadf0bc9 0\tsrc/internal/adapters/privacyidea/enrollment_state_test.go" + }, + "src/internal/adapters/privacyidea/optional_mfa_test.go": { + "sha256": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65", + "index": "100644 13316d5ffada681a8f956b519f5a3d6ef94c3233 0\tsrc/internal/adapters/privacyidea/optional_mfa_test.go" + }, + "src/internal/adapters/privacyidea/read_scope_test.go": { + "sha256": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3", + "index": "100644 964fbb76048c44616d56b7da916d44c6b86432f0 0\tsrc/internal/adapters/privacyidea/read_scope_test.go" + }, + "src/internal/adapters/tenantengine/adapter.go": { + "sha256": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3", + "index": "100644 eeb54b9f0b7e1b9776fc37ecfc36599bbf07026a 0\tsrc/internal/adapters/tenantengine/adapter.go" + }, + "src/internal/adapters/tenantengine/adapter_test.go": { + "sha256": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0", + "index": "100644 d437fd14dcfffa53999b16849888a86c553d0a42 0\tsrc/internal/adapters/tenantengine/adapter_test.go" + }, + "src/internal/authclient/cli.go": { + "sha256": "sha256:376a8e0d1d60825a8b3ef21d371e3fc353c0a34e461712a6899e264278edbe7f", + "index": "100644 2b0417e3745a31e177d2b154454a0f132d2f7616 0\tsrc/internal/authclient/cli.go" + }, + "src/internal/authclient/client.go": { + "sha256": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5", + "index": "100644 0d39586235d89aa8097d76b7d210331e4a53083f 0\tsrc/internal/authclient/client.go" + }, + "src/internal/authclient/client_test.go": { + "sha256": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa", + "index": "100644 dfce39882584ab7f37ecb9878d6b1801b74d9a4c 0\tsrc/internal/authclient/client_test.go" + }, + "src/internal/authclient/verify.go": { + "sha256": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25", + "index": "100644 66b0ea4961b3573152c491f597fad3e02ea59a1a 0\tsrc/internal/authclient/verify.go" + }, + "src/internal/authclient/verify_test.go": { + "sha256": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc", + "index": "100644 a2a824f02554f0645a543934ee33419dca429085 0\tsrc/internal/authclient/verify_test.go" + }, + "src/internal/config/account_test.go": { + "sha256": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d", + "index": "100644 f28ad987f862203708795498947229df9de3757e 0\tsrc/internal/config/account_test.go" + }, + "src/internal/config/config.go": { + "sha256": "sha256:afd465acd9b6879493b888acc6fc54b9a71e032892701a81f1072986d2c30ac7", + "index": "100644 7895b73a96899a5443a0a044108e3dd3e4e76294 0\tsrc/internal/config/config.go" + }, + "src/internal/config/config_test.go": { + "sha256": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100", + "index": "100644 d4236a6d45fa57b865b0983b395c6e9b3974105b 0\tsrc/internal/config/config_test.go" + }, + "src/internal/config/optional_mfa_test.go": { + "sha256": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e", + "index": "100644 585aa6243370bb60a94506f5fa670516959c12b0 0\tsrc/internal/config/optional_mfa_test.go" + }, + "src/internal/config/validate.go": { + "sha256": "sha256:8189ee82e7df74a2a4a1f53e213b1be0a5c737fd76d8f283f7ec9f8ed38c3a65", + "index": "100644 c020e4076637f2f062ae49a8578bea8dd493bd70 0\tsrc/internal/config/validate.go" + }, + "src/internal/domain/assurance.go": { + "sha256": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7", + "index": "100644 afbc48c673df6fe76ed3f2fa2aea492e476ec37b 0\tsrc/internal/domain/assurance.go" + }, + "src/internal/domain/assurance_test.go": { + "sha256": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755", + "index": "100644 b9096ef7c09da6a413ed392c751799b3bb1b72bf 0\tsrc/internal/domain/assurance_test.go" + }, + "src/internal/domain/auth.go": { + "sha256": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee", + "index": "100644 66c907c367651b193559701c2da564aea6757ae8 0\tsrc/internal/domain/auth.go" + }, + "src/internal/domain/conformance_test.go": { + "sha256": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1", + "index": "100644 5984835dd46da65b095829e05948f841554f733f 0\tsrc/internal/domain/conformance_test.go" + }, + "src/internal/domain/mfa.go": { + "sha256": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87", + "index": "100644 30b0a92d9444324cdf67df1e856feb984385c29e 0\tsrc/internal/domain/mfa.go" + }, + "src/internal/domain/model.go": { + "sha256": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029", + "index": "100644 1cfda5371fe0c6eec07329b4990d3c6a105e92ee 0\tsrc/internal/domain/model.go" + }, + "src/internal/domain/repository.go": { + "sha256": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011", + "index": "100644 56252d821062128e3323a0cdca1064d292a97898 0\tsrc/internal/domain/repository.go" + }, + "src/internal/errors/taxonomy.go": { + "sha256": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c", + "index": "100644 80ebf0763538369dff67abd3a17c6269022f5e2e 0\tsrc/internal/errors/taxonomy.go" + }, + "src/internal/errors/taxonomy_test.go": { + "sha256": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45", + "index": "100644 2f5722e94e982c71b008b7f4c522d500df76a0e1 0\tsrc/internal/errors/taxonomy_test.go" + }, + "src/internal/jose/jose.go": { + "sha256": "sha256:bcfc30d2175735f37e7ea6f58451e122fbdfe02c562805e5a018c863fb677fab", + "index": "100644 14769bbe206e8fbbf6eb0ed4212e3ad656ffd996 0\tsrc/internal/jose/jose.go" + }, + "src/internal/jose/jose_test.go": { + "sha256": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43", + "index": "100644 34abfa36f8dd355df7713eb940b269f785805402 0\tsrc/internal/jose/jose_test.go" + }, + "src/internal/migration/lldapexport/exporter.go": { + "sha256": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103", + "index": "100644 4119e8a8950a8461c5fe5cb4944af498df5df84f 0\tsrc/internal/migration/lldapexport/exporter.go" + }, + "src/internal/migration/lldapexport/exporter_test.go": { + "sha256": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2", + "index": "100644 e9071d133a6f3fbd2421d3d0dd9fc8a051d61693 0\tsrc/internal/migration/lldapexport/exporter_test.go" + }, + "src/internal/migration/tokeycloak/transformer.go": { + "sha256": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa", + "index": "100644 4666a2b8579e73f76bd4ccf4518cdd9818035e2a 0\tsrc/internal/migration/tokeycloak/transformer.go" + }, + "src/internal/migration/tokeycloak/transformer_test.go": { + "sha256": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b", + "index": "100644 50dee2c66b1922e2d109ca963592fe3a0bf9711c 0\tsrc/internal/migration/tokeycloak/transformer_test.go" + }, + "src/internal/migration/toldap/generator.go": { + "sha256": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46", + "index": "100644 fd21830fd2299a401000bb418946d107bbefa1ee 0\tsrc/internal/migration/toldap/generator.go" + }, + "src/internal/migration/toldap/generator_test.go": { + "sha256": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc", + "index": "100644 5ec399dca0db878f3d5fab76504947635770727d 0\tsrc/internal/migration/toldap/generator_test.go" + }, + "src/internal/server/errors/enforcement.go": { + "sha256": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2", + "index": "100644 81fd2c8d24016adb626f7a88b442361cf6197124 0\tsrc/internal/server/errors/enforcement.go" + }, + "src/internal/server/errors/enforcement_test.go": { + "sha256": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c", + "index": "100644 0f8384bc5a147905ea99f33d1a4e573c7a85387e 0\tsrc/internal/server/errors/enforcement_test.go" + }, + "src/internal/server/oidc/account.go": { + "sha256": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a", + "index": "100644 7036039edd2837cb22e804b60633b2425a5fb817 0\tsrc/internal/server/oidc/account.go" + }, + "src/internal/server/oidc/account_test.go": { + "sha256": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763", + "index": "100644 1a2d68f1008418e138a6c2d293f2afda017c5ba5 0\tsrc/internal/server/oidc/account_test.go" + }, + "src/internal/server/oidc/assurance_time_test.go": { + "sha256": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a", + "index": "100644 72ad579561c84e9ed99da98dd86ca472880ff123 0\tsrc/internal/server/oidc/assurance_time_test.go" + }, + "src/internal/server/oidc/audience_test.go": { + "sha256": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3", + "index": "100644 fb5b1f41c0619b7ea6d72c231aec2027769f7f77 0\tsrc/internal/server/oidc/audience_test.go" + }, + "src/internal/server/oidc/authorize.go": { + "sha256": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e", + "index": "100644 60447bc9cd056c2119b1611e38585bc81884a568 0\tsrc/internal/server/oidc/authorize.go" + }, + "src/internal/server/oidc/authorize_test.go": { + "sha256": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d", + "index": "100644 434b51089fe92497644cdc4504f765f75c21f542 0\tsrc/internal/server/oidc/authorize_test.go" + }, + "src/internal/server/oidc/discovery.go": { + "sha256": "sha256:5ff9ddd9e2f4304b562e116100ca31fe7eabf8315d7d73998d3e6c5bea53956d", + "index": "100644 4b0074b6b5376f11c3979b13b05761c521762bac 0\tsrc/internal/server/oidc/discovery.go" + }, + "src/internal/server/oidc/discovery_test.go": { + "sha256": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0", + "index": "100644 f2d069d54f67ac1cad061e53687a44ab0c1648ba 0\tsrc/internal/server/oidc/discovery_test.go" + }, + "src/internal/server/oidc/handoff.go": { + "sha256": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4", + "index": "100644 31954ca4dd03ccc36d9da03d5734ac029d46c9ce 0\tsrc/internal/server/oidc/handoff.go" + }, + "src/internal/server/oidc/handoff_test.go": { + "sha256": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02", + "index": "100644 d8a33fb4e5f90f92e7acab2f198c540d39cfdebc 0\tsrc/internal/server/oidc/handoff_test.go" + }, + "src/internal/server/oidc/hardening_test.go": { + "sha256": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e", + "index": "100644 d73a3ffcd40558731fb4fac2c66ff0588c0969f5 0\tsrc/internal/server/oidc/hardening_test.go" + }, + "src/internal/server/oidc/human_tenant_test.go": { + "sha256": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5", + "index": "100644 187bd993cb85369ada263c734bd4e7daec5bbb73 0\tsrc/internal/server/oidc/human_tenant_test.go" + }, + "src/internal/server/oidc/jwks.go": { + "sha256": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60", + "index": "100644 e74bd2e4a219c25aefc781841e73eb219e0945a5 0\tsrc/internal/server/oidc/jwks.go" + }, + "src/internal/server/oidc/jwks_test.go": { + "sha256": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa", + "index": "100644 1fd53f19e6c8a131a639d780bea7e1c0c609930a 0\tsrc/internal/server/oidc/jwks_test.go" + }, + "src/internal/server/oidc/login_session.go": { + "sha256": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f", + "index": "100644 7953f06b4948162868166a8ffafef36b2445509b 0\tsrc/internal/server/oidc/login_session.go" + }, + "src/internal/server/oidc/logout.go": { + "sha256": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893", + "index": "100644 496d237a3f642ac94c869f0dc3438d9a2e262b9d 0\tsrc/internal/server/oidc/logout.go" + }, + "src/internal/server/oidc/native_optional_test.go": { + "sha256": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5", + "index": "100644 47d4df2dee47489a0923df57fe654be87edf2b12 0\tsrc/internal/server/oidc/native_optional_test.go" + }, + "src/internal/server/oidc/optional_mfa_test.go": { + "sha256": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529", + "index": "100644 2f8eecda6f29cf3d31177ec3b7ec3650a871b555 0\tsrc/internal/server/oidc/optional_mfa_test.go" + }, + "src/internal/server/oidc/policy_isolation_test.go": { + "sha256": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077", + "index": "100644 221ed02325c872a7947e798607da236f15d7d6e3 0\tsrc/internal/server/oidc/policy_isolation_test.go" + }, + "src/internal/server/oidc/policy_runtime_test.go": { + "sha256": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a", + "index": "100644 c70d6a4a7d78abd6c0911776f8857ddedb7858bd 0\tsrc/internal/server/oidc/policy_runtime_test.go" + }, + "src/internal/server/oidc/principal_type_guard_test.go": { + "sha256": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40", + "index": "100644 d1c9a9660f3e5fe6c878657957ce6bd7db175c6d 0\tsrc/internal/server/oidc/principal_type_guard_test.go" + }, + "src/internal/server/oidc/session.go": { + "sha256": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c", + "index": "100644 15d759c148e72d4a84df18a3d62db1c6838d1be5 0\tsrc/internal/server/oidc/session.go" + }, + "src/internal/server/oidc/signin_diagnosis.go": { + "sha256": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f", + "index": "100644 ba82ee4b244e241d8d156e05315d8b71626b1cef 0\tsrc/internal/server/oidc/signin_diagnosis.go" + }, + "src/internal/server/oidc/signin_diagnosis_test.go": { + "sha256": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e", + "index": "100644 72a1b3518b8824da04d1bf87bd4d73dfa1a7a934 0\tsrc/internal/server/oidc/signin_diagnosis_test.go" + }, + "src/internal/server/oidc/tenant_precondition_test.go": { + "sha256": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f", + "index": "100644 a955146312249d526fa7fedefd085c1140399ac0 0\tsrc/internal/server/oidc/tenant_precondition_test.go" + }, + "src/internal/server/oidc/tenant_test.go": { + "sha256": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713", + "index": "100644 1517610493b1e91c16f9bfee5fd63361be2d7992 0\tsrc/internal/server/oidc/tenant_test.go" + }, + "src/internal/server/oidc/token.go": { + "sha256": "sha256:8422cc9dbd39c88dfe835b43f6262cb120bc0d2e5b08898a151167b7555368d3", + "index": "100644 b1d7f57106208d5cc8a80d964e1698d1dc59dd91 0\tsrc/internal/server/oidc/token.go" + }, + "src/internal/server/oidc/token_test.go": { + "sha256": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33", + "index": "100644 42314ec0fe6c254c3b29c4f859e090f18b316f21 0\tsrc/internal/server/oidc/token_test.go" + }, + "src/internal/server/oidc/userinfo.go": { + "sha256": "sha256:bbd5fa9ebd0726371c90d6938536c1ce5fcfacbbe853a80833e3587fc2115a0b", + "index": "100644 c3d2014bd63dc2186d8a2123cef064b2cddfef36 0\tsrc/internal/server/oidc/userinfo.go" + }, + "src/internal/server/oidc/userinfo_test.go": { + "sha256": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80", + "index": "100644 5a12f5e898c8f4526d505404eed14e6f466ca7d7 0\tsrc/internal/server/oidc/userinfo_test.go" + }, + "src/internal/server/policy/policy.go": { + "sha256": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7", + "index": "100644 29167da1dbb2b3a9d2e74f5f0319e0902d4173a3 0\tsrc/internal/server/policy/policy.go" + }, + "src/internal/server/policy/policy_test.go": { + "sha256": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2", + "index": "100644 4becee709136b31735eeeab3f1b6b1875de4407e 0\tsrc/internal/server/policy/policy_test.go" + }, + "src/internal/server/readiness/readiness.go": { + "sha256": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073", + "index": "100644 e0cfefbf7c014370890470fc42850c3b3a879daa 0\tsrc/internal/server/readiness/readiness.go" + }, + "src/internal/server/readiness/readiness_test.go": { + "sha256": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6", + "index": "100644 02cf96ed77554ce25eb4f62e54c00b02cc0089a5 0\tsrc/internal/server/readiness/readiness_test.go" + }, + "src/internal/server/telemetry/emitter.go": { + "sha256": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0", + "index": "100644 ff11c05a76db4692e1de6d9ba19f5dc5d45ed2e8 0\tsrc/internal/server/telemetry/emitter.go" + }, + "src/internal/server/telemetry/emitter_test.go": { + "sha256": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a", + "index": "100644 3d64e3d148536f96bddecd228c7eba6711194780 0\tsrc/internal/server/telemetry/emitter_test.go" + }, + "src/internal/server/telemetry/events.go": { + "sha256": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167", + "index": "100644 793b79bcef80638f2c182645cba9773c17c1550c 0\tsrc/internal/server/telemetry/events.go" + }, + "src/internal/validator/report.go": { + "sha256": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8", + "index": "100644 87baa854418f0a6617579e6566aa84217134c6e2 0\tsrc/internal/validator/report.go" + }, + "src/internal/validator/validator.go": { + "sha256": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f", + "index": "100644 78eb866cef192174b0a29f151eac64f4adc85472 0\tsrc/internal/validator/validator.go" + }, + "src/internal/validator/validator_test.go": { + "sha256": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276", + "index": "100644 a86eff31eb55d9cda811adc3508b44e82f6b2eba 0\tsrc/internal/validator/validator_test.go" + }, + "src/tests/conformance/external_test.go": { + "sha256": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb", + "index": "100644 75cddf3602a0dbb1d29162db1df187e43e3d4568 0\tsrc/tests/conformance/external_test.go" + }, + "src/tests/migration/fixtures_test.go": { + "sha256": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de", + "index": "100644 208e903a6377e3b894595de8c97248ba96e3dbb3 0\tsrc/tests/migration/fixtures_test.go" + }, + "src/tests/migration/scenario_b_test.go": { + "sha256": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0", + "index": "100644 8a49ddec84f156a2ef0badf8f78d292243b7a5dd 0\tsrc/tests/migration/scenario_b_test.go" + }, + "src/tests/migration/scenario_c_test.go": { + "sha256": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80", + "index": "100644 bf0a61a1c7e00fd6f3e919529603c5c7461f4a69 0\tsrc/tests/migration/scenario_c_test.go" + }, + "src/tests/negative/negative_test.go": { + "sha256": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f", + "index": "100644 377b0f33da592cfbc7eeb9bc74025280ce553a6d 0\tsrc/tests/negative/negative_test.go" + }, + "src/tests/profile/profile_test.go": { + "sha256": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6", + "index": "100644 8b802f71346bf27a58ae73877fa594be0557b396 0\tsrc/tests/profile/profile_test.go" + }, + "tools/register-informed-decision.py": { + "sha256": "sha256:c606e58d3fd3e305fe8e932907f341447b2d2e41dd1b11f1d008672e9271299d", + "index": "100644 9192fd9cde67896f741cf67dccd48d2250d6ad27 0\ttools/register-informed-decision.py" + }, + "tools/rotate-live-exposed-secret.sh": { + "sha256": "sha256:c9505e529fc7baa5b6aded0ed2d260af13a7238e8059e9c2a759e3bba7a35833", + "index": "100755 0dcb542eeb16827ccb0d0f05b6e302f6af1693e8 0\ttools/rotate-live-exposed-secret.sh" + }, + "wiki/KeyCapeSpecificationPack_v0.1.md": { + "sha256": "sha256:0001a2964627c0d1ba0fe0f4ac3cc18b87a4536e6ac75e9b2539e844cbb716f3", + "index": "100644 4b728fd406c815143f302a76512858d3577e6aca 0\twiki/KeyCapeSpecificationPack_v0.1.md" + }, + "wiki/KeyCapeSpecification_v0.1.md": { + "sha256": "sha256:a76de79a37ac799192af2351d929a85cbf13c62ca37cf8b86c589b773a48259c", + "index": "100644 c803cabd2342689c3123e71b8773edb8a972125d 0\twiki/KeyCapeSpecification_v0.1.md" + }, + "workplans/ADHOC-2026-09-05.md": { + "sha256": "sha256:76123d22d0c1efc51212f8914e386f078b3c6d908959af6978ae73874e492b04", + "index": "100644 f7e78675df62c9b48e29f882fa99298d6d8ce7f1 0\tworkplans/ADHOC-2026-09-05.md" + }, + "workplans/ADHOC-2026-09-07.md": { + "sha256": "sha256:c335ebfe91dad291277e8f17034ac8f785b3bc767f281f205c446c08f03d7978", + "index": "100644 17401073c053c9710e6e680243bbd313b08d1fc1 0\tworkplans/ADHOC-2026-09-07.md" + }, + "workplans/ADHOC-2026-09-27.md": { + "sha256": "sha256:2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314", + "index": "100644 0b755547aa7d5589f0898df2f102a45b6aa24838 0\tworkplans/ADHOC-2026-09-27.md" + }, + "workplans/KEY-WP-0001-keycape-implementation.md": { + "sha256": "sha256:29a2bf4892b7185e28e9126a3d8311d0fed34a876f416a3e75c605915fde0969", + "index": "100644 74334411cd056e7c7253e84f1c4b40c42eb13de3 0\tworkplans/KEY-WP-0001-keycape-implementation.md" + }, + "workplans/KEY-WP-0002-container-image-gitea.md": { + "sha256": "sha256:76fee187185b30219bc257ccc9b587661df4489574a2bb57703a365a5d803cec", + "index": "100644 418f477c0ede13d7c003d744abf234347f5d5e31 0\tworkplans/KEY-WP-0002-container-image-gitea.md" + }, + "workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md": { + "sha256": "sha256:5e949c696e587540d305b682eb1f8390ec544bbec6a566be8d04e73fb1b254ca", + "index": "100644 9896c67d78f0efdd03325535eb41295250be3a19 0\tworkplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md" + }, + "workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md": { + "sha256": "sha256:260ba7d3116db3bc5a72e5cd8b9984137e7e372c8128d21073614dbd948c10c2", + "index": "100644 a50e910558ee43073c92cb27865a1aceac990dd5 0\tworkplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md" + }, + "workplans/KEY-WP-0005-iam-profile-core-claims.md": { + "sha256": "sha256:c6702b644cdd046c0d3bba8ba8837050826e4dc3d855bd73327ae94218ba5b57", + "index": "100644 648d2405699dcf25b3121df6c46d871597100d7b 0\tworkplans/KEY-WP-0005-iam-profile-core-claims.md" + }, + "workplans/KEY-WP-0006-client-credentials-service-tokens.md": { + "sha256": "sha256:b3227849978c95cad89e02afb74bf2217b6188ae65c5b0af6a21703d65477701", + "index": "100644 632c4c2cad39cbde55c2ae1f2ba5ecf2f445b1a0 0\tworkplans/KEY-WP-0006-client-credentials-service-tokens.md" + }, + "workplans/KEY-WP-0007-user-engine-portal-oidc-client.md": { + "sha256": "sha256:5408ff6dfeb58a8a7756b48fe4f54f7c7b98d7940cb9aaa5c7864f1247e58150", + "index": "100644 f1b04e1a61b398426c23d8d9ca655b3937d7bd10 0\tworkplans/KEY-WP-0007-user-engine-portal-oidc-client.md" + }, + "workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md": { + "sha256": "sha256:bfc9b2118f0712e92a86bbb0f14a2857fafc3a42a3c14c0e2535002aaa57a2f5", + "index": "100644 438ab5480de3bd66d75b6ec7e09639d1b93e3e5f 0\tworkplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md" + }, + "workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md": { + "sha256": "sha256:d1daac9815930104a392ea96c14922e34f947720a31d1b0ac45a770997e0f476", + "index": "100644 c9e585db65424c7c4e3b5adf37a68de2bdfbdc1c 0\tworkplans/KEY-WP-0009-provider-capabilities-and-service-identities.md" + }, + "workplans/KEY-WP-0010-openbao-operator-loopback-callback.md": { + "sha256": "sha256:41d28a16bef9313b776c5c6305316a9e7d4f5528b7fb7427b8384a0ed0ad35c4", + "index": "100644 81614eeb8c98205d6e5eb3201da10e36b76a21e9 0\tworkplans/KEY-WP-0010-openbao-operator-loopback-callback.md" + }, + "workplans/KEY-WP-0011-live-secret-exposure-recovery.md": { + "sha256": "sha256:3c5458180fe81a04e357f8db737e4287f79640b5ab02682bcadc595b73f846cd", + "index": "100644 f7b906f552f6e3279895a90afbe216b6e78ee5ff 0\tworkplans/KEY-WP-0011-live-secret-exposure-recovery.md" + }, + "workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md": { + "sha256": "sha256:68d34d75c914e7d2d8177000e800d6372cccda2b850c03ab85da42b90a66b91c", + "index": "100644 0f3deb626e5de9a9f898754ef81d3f2ee5282792 0\tworkplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md" + }, + "workplans/KEY-WP-0013-approval-engine-resource-audience.md": { + "sha256": "sha256:55c88bf4ed9a456db59b319bbaf960dfe6f4147437e339f950e0583d7582057a", + "index": "100644 7d77aee3caf3d79fe6d6c1369249c6d94e0ae123 0\tworkplans/KEY-WP-0013-approval-engine-resource-audience.md" + }, + "workplans/KEY-WP-0014-native-credential-lane-handoff.md": { + "sha256": "sha256:22aac43ae5039aebd11f9f0aa57a882ad009ad6ed119ec08eb85b32a5af48cd7", + "index": "100644 bacf04c07f59125a90c519d3c306a7414cb9a1a3 0\tworkplans/KEY-WP-0014-native-credential-lane-handoff.md" + }, + "workplans/KEY-WP-0015-scope-intent-assessment.md": { + "sha256": "sha256:60b9f2265eff880d916c3ee8dba36aec9984ec310804de776cebf89cc98fdb95", + "index": "100644 82b952a216346ffb7f5cab413623fe4a88f7700e 0\tworkplans/KEY-WP-0015-scope-intent-assessment.md" + }, + "workplans/KEY-WP-0016-authorization-code-protocol-hardening.md": { + "sha256": "sha256:ed48128a1cb51cbe3d154727bafd5a5ffb3b5b618c20649b2a0e7db154cf3a0c", + "index": "100644 52839bd0f83d97656c8ad97a8ffc338d9019239a 0\tworkplans/KEY-WP-0016-authorization-code-protocol-hardening.md" + }, + "workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md": { + "sha256": "sha256:8700e0e5aa07dcd7f651e792cca12a0330b7ee9d2f6064b4cc3fcf4c7d509340", + "index": "100644 00eb03971aae75724afbf7b8a4946ce6b7eda3af 0\tworkplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md" + }, + "workplans/KEY-WP-0018-export-completeness-evidence.md": { + "sha256": "sha256:15026b708d116f803b0b2fa43f30a4dbced76e4d8137b5fcb5b4275e593d7c7b", + "index": "100644 325fc483d62dbad53b92b986de56112168a92b4a 0\tworkplans/KEY-WP-0018-export-completeness-evidence.md" + }, + "workplans/KEY-WP-0019-upstream-provider-token-verification.md": { + "sha256": "sha256:0d2d8b5bda1e0d333935812d8d3749cb7b0c5523a3987d1b26185f9f6d0f0fa2", + "index": "100644 dfeb92296f46d4ce2ac7df1fedcc61e9ccc5d6b3 0\tworkplans/KEY-WP-0019-upstream-provider-token-verification.md" + }, + "workplans/KEY-WP-0020-migration-contract-preservation.md": { + "sha256": "sha256:cc842d15bf42ddc4d0f4056bc19b3176cab2c5f3c1a2fe6d648e5dbd077be8b3", + "index": "100644 7b6f3be976293469dee2180c10e16f313876d640 0\tworkplans/KEY-WP-0020-migration-contract-preservation.md" + }, + "workplans/KEY-WP-0021-snapshot-attribute-validation.md": { + "sha256": "sha256:b603248693073a9b9b25b4d0a4147d8d88c16a07dc9936e48fa1f3c76fe8bab6", + "index": "100644 1f6efeb32a6eb8fcbc52f4d3b591e2fc6b9e006d 0\tworkplans/KEY-WP-0021-snapshot-attribute-validation.md" + }, + "workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md": { + "sha256": "sha256:8c135e84be81de31b4949f52ac28dccd404f2e4c3eaa576d733bd50160427a53", + "index": "100644 7c69629ecd20d0158aa1332d9d1442b796f6a7af 0\tworkplans/KEY-WP-0022-replacement-harness-and-external-conformance.md" + }, + "workplans/KEY-WP-0023-live-migration-proof.md": { + "sha256": "sha256:e581cd11f3905805b5e8ba8e2ede85092577c7d086e1761f2a2d033fdead4e47", + "index": "100644 3e7c83cb12e7fc31e682cf3c27f4c4767ded2905 0\tworkplans/KEY-WP-0023-live-migration-proof.md" + }, + "workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md": { + "sha256": "sha256:af7842fa97263bfd46a4b5ae84c5c1ecddb06ab9623586e0292dcc93099fea35", + "index": "100644 baa0bbd195a92708f2befd027b938fcd1812ced7 0\tworkplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md" + }, + "workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md": { + "sha256": "sha256:0addb34e6d85e376c6a5db7afba597b6f268afa98b86c8c60add9be174dd1b26", + "index": "100644 a53e646761178748e8c4b00dcde888698447a948 0\tworkplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md" + }, + "workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md": { + "sha256": "sha256:c1bc0de47511ae49102009a172c84d9d5d64e829a18c8d40564fe817935563b3", + "index": "100644 1f96a2ac3ac939c4e6e0f66172477cf73db04e51 0\tworkplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md" + }, + "workplans/KEY-WP-0027-rollout-readiness-and-live-state.md": { + "sha256": "sha256:f559eae61d433d451f80ea575254bb14a47f2485267b412e42d524b434bb1b59", + "index": "100644 57e01b2db033847cc786504b8795e37112d988a7 0\tworkplans/KEY-WP-0027-rollout-readiness-and-live-state.md" + }, + "workplans/KEY-WP-0028-browser-client-field-validation.md": { + "sha256": "sha256:fbd51422fa5e6449000e4520e6639f6135c7393165a5ae02b1e92892edba0480", + "index": "100644 46b0672fea90cd7cb672f7d119706c240474f8ca 0\tworkplans/KEY-WP-0028-browser-client-field-validation.md" + }, + "workplans/KEY-WP-0029-scope-reassessment.md": { + "sha256": "sha256:3241f3b94db9cad5e3770cf1fedc5c211288357e4f69b5ba9da57065094bdf0e", + "index": "100644 392f97750f6cb8b12868a6fd016c7d396b58477e 0\tworkplans/KEY-WP-0029-scope-reassessment.md" + }, + "workplans/KEY-WP-0030-tenant-precondition-guard.md": { + "sha256": "sha256:c94c3afc7fb4270dfc18f3fe8a32946c524e4a6ba2d02a3d8fda69c9b8441af0", + "index": "100644 0f74e0d5a037eec64b11b57c67b02057a3b0bfef 0\tworkplans/KEY-WP-0030-tenant-precondition-guard.md" + }, + "workplans/KEY-WP-0031-gate-house-tenant-ruling.md": { + "sha256": "sha256:0abb135367f0366aa071769c4bd76fdeb321d810ec75ad3b58d718734f78382b", + "index": "100644 d2ede9d494927976ccb9ba6a5aeaf1ed21c30837 0\tworkplans/KEY-WP-0031-gate-house-tenant-ruling.md" + }, + "workplans/KEY-WP-0032-principal-type-provenance-guard.md": { + "sha256": "sha256:6a5cd483a40b9477227b02156c42b3469094dac145b33727b336118898aa782a", + "index": "100644 a04f52f817a51efc1d0d0ecb99efacea8dc53a17 0\tworkplans/KEY-WP-0032-principal-type-provenance-guard.md" + }, + "workplans/KEY-WP-0033-vergabe-fresh-login.md": { + "sha256": "sha256:c01bb51cf2dcad06ea846040dddef09845041fa7271cd7015e5a34a49e60bb8f", + "index": "100644 5b478f953890339aec796c11a536af0ec3238789 0\tworkplans/KEY-WP-0033-vergabe-fresh-login.md" + }, + "workplans/KEY-WP-0034-account-recovery.md": { + "sha256": "sha256:7db9adcd418ccabfbce672aec283249a6e374212492f0349e8c47a568f130eda", + "index": "100644 eb58f99bc26e3c3ac33230dfe432c8b1fabe47ce 0\tworkplans/KEY-WP-0034-account-recovery.md" + }, + "workplans/KEY-WP-0035-optional-mfa.md": { + "sha256": "sha256:c60ea8d0c195686c0d2317f657aad2de27c9b9432d311627c0e1d06124cabb49", + "index": "100644 29eb711f656c91808e91f3981a9168c3a0ebc253 0\tworkplans/KEY-WP-0035-optional-mfa.md" + }, + "workplans/KEY-WP-0036-platform-factor-recovery.md": { + "sha256": "sha256:5ceb08fe5789b4f02550bb04fcaf8eeb25a43ac3f506dfc62d60a513bcd0013f", + "index": "100644 fd296bc05a6e565b0ef6e88a9ae3daa7c9f81786 0\tworkplans/KEY-WP-0036-platform-factor-recovery.md" + }, + "workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md": { + "sha256": "sha256:3a90a0c460fc439570820b96d1ddc3b7f8646c557ca3a8aa27c6d546bb1a845a", + "index": "100644 ad7fed77a27aa8487693ad6fcf342502f418427a 0\tworkplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md" + }, + "workplans/KEY-WP-0038-forgejo-oidc-client.md": { + "sha256": "sha256:dc8d88d462799e7e6e44da759a973d52e44102a271969a5daaa9d30ec015b064", + "index": "100644 8cd5bc66f7b0115eec7813d6eec994a1bb618668 0\tworkplans/KEY-WP-0038-forgejo-oidc-client.md" + }, + "workplans/KEY-WP-0039-openbao-negative-probe-tokens.md": { + "sha256": "sha256:517f9526d574ce3faafad18837e0039db8f3f7990eac3e34a1678cdd7307a9e1", + "index": "100644 4398517afe04e3b0723722af37b434fa8d2209ab 0\tworkplans/KEY-WP-0039-openbao-negative-probe-tokens.md" + }, + "workplans/KEY-WP-0040-mfa-attended-run-findings.md": { + "sha256": "sha256:98b23d8810bff047067169232213cb313ecc14ff79751224842723afe808fbd2", + "index": "100644 8e8730c8e7733b13d6a9f42689a9d8e407769e81 0\tworkplans/KEY-WP-0040-mfa-attended-run-findings.md" + }, + "workplans/KEY-WP-0041-sign-in-diagnosis-page.md": { + "sha256": "sha256:7e44f46acd6b3942c1852b49e0d64b205dfd7d9bb378aed5241b1bf22d7ce88a", + "index": "100644 56fd05819e3acd37b8e6cb468ec3d62307f1ae7a 0\tworkplans/KEY-WP-0041-sign-in-diagnosis-page.md" + } + }, + "tracked_status": "" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/consistency-result-summary.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/consistency-result-summary.json new file mode 100644 index 0000000..255a0ec --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/consistency-result-summary.json @@ -0,0 +1,27 @@ +{ + "command": [ + "statehub", + "fix-consistency", + "--repo", + "key-cape", + "--json" + ], + "source_revision": "d803e9282a47b49daf930fb905312fa5104c0ace", + "exit_code": 0, + "result": "warn", + "summary": { + "fail": 0, + "automation_error": 0, + "warn": 8, + "info": 1 + }, + "relevant_fixes_applied": [ + "bindings: synced 44/44", + "C-11 skipped: task 'ADHOC-2026-09-27-T02' in finished workstream \u2014 not created", + "brief: .custodian-brief.md updated", + "push: pushed" + ], + "writeback_commit_warning": "Generated-only commits rejected: staged", + "interpretation": "No historical task created or missing reference repaired by the normal closed-workstream branch. Retained initial refusal; exact existing binding subsequently restored through official injection helper in candidate, then ordinary source publication. No hook bypass.", + "receipt_scope": "selected exact terminal command fields; full command output retained in tool transcript" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding-restoration-proposal.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding-restoration-proposal.json new file mode 100644 index 0000000..20eac2e --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding-restoration-proposal.json @@ -0,0 +1,14 @@ +{ + "source": "workplans/ADHOC-2026-09-27.md", + "task_record_id": "ADHOC-2026-09-27-T02", + "existing_hub_task_id": "7fa0bfc7-0772-57c7-8d6f-641615cab82a", + "parent": "5217dd06-73cb-50f8-ba64-528e421b29de", + "title_status_description_exact_match": true, + "same_all_other_task_fields": true, + "tool": "State Hub consistency_check._inject_task_id_into_block", + "new_task_or_uuid_created": false, + "canonical_owner_changed": false, + "reason": "normal C-11 deliberately skips closed workstreams; restore only an already-existing exact source/cached binding", + "source_before_sha256": "sha256:2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314", + "source_after_sha256": "sha256:f6220f17fc5b6e7300d0a55ac29f78f385ad89db276ccaa21693428fffa5919b" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding.json new file mode 100644 index 0000000..e83c3b4 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-consistency/existing-binding.json @@ -0,0 +1,23 @@ +{ + "workplan_id": "5217dd06-73cb-50f8-ba64-528e421b29de", + "id": "7fa0bfc7-0772-57c7-8d6f-641615cab82a", + "record_id": "ADHOC-2026-09-27-T02", + "flavor": null, + "title": "Discovery/JWKS fetch was missing the split-horizon origin headers", + "description": "Rolling out T01 (image digest `sha256:248e449a...`) broke every KeyCape login,\nincluding unrelated clients (`user-engine-portal`), with `auth_failure` /\n`provider_metadata_unavailable` at `/api/oidc/token` on the KeyCape side and\nAuthelia logging `invalid X-Forwarded-Proto header value 'http'` on\n`GET /.well-known/openid-configuration` from KeyCape's pod IP.\n\nRoot cause, unrelated to T01's change: `AutheliaAdapter.setPublicOriginHeaders`\n(the NK-WP-0041-T02/path-B fix, key-cape@3b0446e) annotates only the token\nexchange (`newTokenRequest`) with `X-Forwarded-Proto`/`X-Forwarded-Host` so\nAuthelia can derive the public issuer for a split-horizon (in-cluster HTTP,\npublic HTTPS) deployment. `idTokenVerifier.get` in `verify.go` \u2014 used for the\nprovider-metadata and JWKS fetches that verify every upstream ID token \u2014 never\ngot the same treatment; it built a bare request with only an `Accept` header.\nAuthelia 4.38 fails closed on the resulting unmarked in-cluster request. This\ngap predates today's session (present since 3b0446e); it surfaced now because\nthis was the first login attempted since that image went live.\n\nFix: extracted `setPublicOriginHeaders` from an `AutheliaAdapter` method to a\npackage-level function taking the target public base URL explicitly, and gave\n`idTokenVerifier` its own resolved `publicBaseURL` (same\n`BrowserBaseURL`-else-`BaseURL` fallback as the adapter) so `get()` applies\nthe same headers. Added `TestDiscoveryFetch_CarriesPublicOriginHeaders`\n(confirmed it fails without the fix, passes with it). Full suite green.\n\nNew image `key-cape@` still needs a rollout to `sso-mfa/k8s/keycape/deployment.yaml`\nbefore platform-root can sign in again.", + "status": "done", + "priority": "high", + "assignee": null, + "due_date": null, + "blocking_reason": null, + "needs_human": false, + "intervention_note": null, + "decision_id": null, + "blocked_on": null, + "wait_until": null, + "parent_task_id": null, + "wait_kind": null, + "created_at": "2026-10-17T06:49:57.662339Z", + "updated_at": "2026-10-17T06:49:57.662339Z", + "workstream_id": "5217dd06-73cb-50f8-ba64-528e421b29de" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-generated-cleanup.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-generated-cleanup.json new file mode 100644 index 0000000..0a2f1f8 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-generated-cleanup.json @@ -0,0 +1,7 @@ +{ + "restored": ".custodian-brief.md", + "scope": "only this turn generated it; initial owner tracked state was clean", + "original_owner_worktree_and_index_preserved": true, + "source_commit_unchanged": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "tracked_status": "" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.json new file mode 100644 index 0000000..7d8ef2a --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.json @@ -0,0 +1,126 @@ +{ + "repo_slug": "key-cape", + "repo_path": "/home/worsch/key-cape", + "source_revision": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "source_revision_reason": null, + "issues": [ + { + "severity": "WARN", + "check_id": "C-24", + "message": "Repo classification advisory: capability_tag 'security' is not a recommended family tag (allowed, check for synonym)", + "file_path": "", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-33", + "message": "WORK-RECORDS.md is missing or stale \u2014 regenerate via fix-consistency (CUST-WP-0061-T04 generated work-record index)", + "file_path": "WORK-RECORDS.md", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": true + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub", + "file_path": "workplans/KEY-WP-0007-user-engine-portal-oidc-client.md", + "db_id": "", + "file_value": "USER-WP-0020", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'NK-WP-0025' is not linked to State Hub", + "file_path": "workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md", + "db_id": "", + "file_value": "NK-WP-0025", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'NK-WP-0030' is not linked to State Hub", + "file_path": "workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md", + "db_id": "", + "file_value": "NK-WP-0030", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'RAILIANCE-WP-0027' is not linked to State Hub", + "file_path": "workplans/KEY-WP-0010-openbao-operator-loopback-callback.md", + "db_id": "", + "file_value": "RAILIANCE-WP-0027", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-06", + "message": "Authoritative workplan e2eecd1c\u2026 is absent from this projection", + "file_path": "workplans/KEY-WP-0042-online-access-token-status.md", + "db_id": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38", + "file_value": "", + "db_value": "", + "fixable": true + }, + { + "severity": "INFO", + "check_id": "C-08", + "message": "Retired DB workstream 'keycape-implementation@retired-20260826' (id=2c9caad8\u2026, status=archived) has no backing workplan file \u2014 projection tombstone, not live work", + "file_path": "", + "db_id": "2c9caad8-2ced-492d-9d63-376387b4b9b0", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-28", + "message": "2 unread inbox message(s) older than 3 day(s): the-custodian:23172cb2, the-custodian:51e9eace", + "file_path": "", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-28", + "message": "2 unread message(s) lack thread_id for supersession tracking: the-custodian:23172cb2, the-custodian:51e9eace", + "file_path": "", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": false + } + ], + "fixes_applied": [ + "bindings: synced 44/45", + "C-17 cleared: pushed 1 backlogged commit(s)", + "C-33 fixed: regenerated WORK-RECORDS.md", + "C-06 fixed: created workstream e2eecd1c\u2026 for KEY-WP-0042, wrote ID to KEY-WP-0042-online-access-token-status.md", + " + task KEY-WP-0042-T01 \u2192 d8af8e77\u2026", + " + task KEY-WP-0042-T02 \u2192 2cade51f\u2026", + " + task KEY-WP-0042-T03 \u2192 a5539bb0\u2026", + "brief: .custodian-brief.md updated", + "push: pushed" + ], + "summary": { + "fail": 0, + "automation_error": 0, + "warn": 9, + "info": 1 + }, + "result": "warn" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.stderr.txt b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.stderr.txt new file mode 100644 index 0000000..3345563 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/consistency.stderr.txt @@ -0,0 +1 @@ +WARN: git commit failed for writeback: Generated-only commits rejected: staged diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.json new file mode 100644 index 0000000..42e7964 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.json @@ -0,0 +1,202 @@ +{ + "ok": true, + "status": "applied", + "repo_slug": "key-cape", + "commit": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "requests": 2, + "elapsed_ms": 8983.3, + "git": { + "upstream": "origin/main", + "behind": 0, + "ahead": 0 + }, + "identifiers": { + "schema": "repo-manager.identifier-assignment.v1", + "ok": true, + "executed": true, + "repo": "key-cape", + "namespace": "helixforge", + "assignments": [], + "historical_invalid_identifiers": [], + "historical_identity_collisions": [], + "files_changed": [] + }, + "receipt": { + "schema": "state-hub.repository-projection-reconcile.v1", + "instance_role": "primary", + "instance_label": "railiance01", + "expected_commit": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "derived_commit": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "outcome": { + "schema": "state-hub.projection-reset.v1", + "repo_slug": "key-cape", + "commit": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "status": "applied", + "counts": { + "created": 0, + "updated": 45, + "retired": 0, + "refused": 0, + "released": 0, + "created_tasks": 0, + "updated_tasks": 3, + "cancelled_tasks": 0, + "created_edges": 0, + "removed_edges": 0, + "unresolved_edges": 1, + "external_edges": 8 + }, + "created": [], + "updated": [ + "ADHOC-2026-09-05", + "ADHOC-2026-09-07", + "ADHOC-2026-09-27", + "KEY-WP-0001", + "KEY-WP-0002", + "KEY-WP-0003", + "KEY-WP-0004", + "KEY-WP-0005", + "KEY-WP-0006", + "KEY-WP-0007", + "KEY-WP-0008", + "KEY-WP-0009", + "KEY-WP-0010", + "KEY-WP-0011", + "KEY-WP-0012", + "KEY-WP-0013", + "KEY-WP-0014", + "KEY-WP-0015", + "KEY-WP-0016", + "KEY-WP-0017", + "KEY-WP-0018", + "KEY-WP-0019", + "KEY-WP-0020", + "KEY-WP-0021", + "KEY-WP-0022", + "KEY-WP-0023", + "KEY-WP-0024", + "KEY-WP-0025", + "KEY-WP-0026", + "KEY-WP-0027", + "KEY-WP-0028", + "KEY-WP-0029", + "KEY-WP-0030", + "KEY-WP-0031", + "KEY-WP-0032", + "KEY-WP-0033", + "KEY-WP-0034", + "KEY-WP-0035", + "KEY-WP-0036", + "KEY-WP-0037", + "KEY-WP-0038", + "KEY-WP-0039", + "KEY-WP-0040", + "KEY-WP-0041", + "KEY-WP-0042" + ], + "retired": [], + "released": [], + "created_tasks": [], + "updated_tasks": [ + "KEY-WP-0042-T01", + "KEY-WP-0042-T02", + "KEY-WP-0042-T03" + ], + "cancelled_tasks": [], + "created_edges": [], + "removed_edges": [], + "unresolved_edges": [ + { + "workplan": "KEY-WP-0010", + "from": "KEY-WP-0010", + "target": "RAILIANCE-WP-0027", + "reason": "missing" + } + ], + "external_edges": [ + { + "workplan": "KEY-WP-0038", + "from": "KEY-WP-0038-T03", + "target": "KEY-WP-0038-T02", + "relationship_type": "starts_after", + "from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9", + "from_task_id": "40ddaa29-7245-508c-ad48-ddc063e25115", + "to_workplan_id": null, + "to_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30" + }, + { + "workplan": "KEY-WP-0038", + "from": "KEY-WP-0038-T02", + "target": "KEY-WP-0038-T01", + "relationship_type": "starts_after", + "from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9", + "from_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30", + "to_workplan_id": null, + "to_task_id": "5f019d13-1fa3-5b88-a27a-75a3339ca96e" + }, + { + "workplan": "KEY-WP-0038", + "from": "KEY-WP-0038-T02", + "target": "NK-WP-0048-T06", + "relationship_type": "starts_after", + "from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9", + "from_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30", + "to_workplan_id": null, + "to_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c" + }, + { + "workplan": "KEY-WP-0039", + "from": "KEY-WP-0039-T02", + "target": "KEY-WP-0038-T03", + "relationship_type": "starts_after", + "from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86", + "from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3", + "to_workplan_id": null, + "to_task_id": "40ddaa29-7245-508c-ad48-ddc063e25115" + }, + { + "workplan": "KEY-WP-0039", + "from": "KEY-WP-0039-T02", + "target": "KEY-WP-0039-T01", + "relationship_type": "starts_after", + "from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86", + "from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3", + "to_workplan_id": null, + "to_task_id": "4d06b059-3768-53bb-83a4-a9a71c0850b8" + }, + { + "workplan": "KEY-WP-0039", + "from": "KEY-WP-0039-T02", + "target": "KEY-WP-0038-T02", + "relationship_type": "starts_after", + "from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86", + "from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3", + "to_workplan_id": null, + "to_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30" + }, + { + "workplan": "KEY-WP-0007", + "from": "KEY-WP-0007", + "target": "KEY-WP-0005", + "relationship_type": "blocks", + "from_workplan_id": "24db4178-705c-5b09-b7db-ee7a345d71a0", + "from_task_id": null, + "to_workplan_id": "711a8303-69aa-5db6-9043-92530c6dc81b", + "to_task_id": null + }, + { + "workplan": "KEY-WP-0009", + "from": "KEY-WP-0009", + "target": "KEY-WP-0006", + "relationship_type": "blocks", + "from_workplan_id": "c1a9b1cc-2ff0-566a-b544-1a2ef967fc0d", + "from_task_id": null, + "to_workplan_id": "d8d20d8f-61e0-54e2-9a88-a3958f0b6f2d", + "to_task_id": null + } + ], + "refused": [], + "notes": [] + } + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.stderr.txt b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/projection.stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-commit.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-commit.json new file mode 100644 index 0000000..995f390 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-commit.json @@ -0,0 +1,81 @@ +{ + "exit_code": 0, + "commit": "67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "base": "d803e9282a47b49daf930fb905312fa5104c0ace", + "files": [ + { + "path": "SCOPE.md", + "sha256": "a80e80ba2e5279ba50232e6b3f57a5bdd14866e584d2d055dfb09e98a4c24ce0" + }, + { + "path": "docs/online-token-status.md", + "sha256": "f5e41b47500b15c80b2da6cbe37a9e64bcbde1c072750934cf74464a19488925" + }, + { + "path": "docs/operations.md", + "sha256": "3659362e3c7f8720e52cc8dc7c67a6295ac26462f383c68c06c120fac6e6e472" + }, + { + "path": "src/cmd/keycape/main.go", + "sha256": "2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d" + }, + { + "path": "src/internal/config/config.go", + "sha256": "19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683" + }, + { + "path": "src/internal/config/token_status_test.go", + "sha256": "b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5" + }, + { + "path": "src/internal/config/validate.go", + "sha256": "2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405" + }, + { + "path": "src/internal/server/oidc/discovery.go", + "sha256": "00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5" + }, + { + "path": "src/internal/server/oidc/token.go", + "sha256": "304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f" + }, + { + "path": "src/internal/server/oidc/token_status.go", + "sha256": "d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3" + }, + { + "path": "src/internal/server/oidc/token_status_discovery_test.go", + "sha256": "27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031" + }, + { + "path": "src/internal/server/oidc/token_status_http.go", + "sha256": "a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97" + }, + { + "path": "src/internal/server/oidc/token_status_internal_test.go", + "sha256": "3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53" + }, + { + "path": "src/internal/server/oidc/token_status_test.go", + "sha256": "e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90" + }, + { + "path": "src/internal/server/oidc/token_status_transport_test.go", + "sha256": "3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347" + }, + { + "path": "src/internal/server/oidc/userinfo.go", + "sha256": "f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f" + }, + { + "path": "workplans/ADHOC-2026-09-27.md", + "sha256": "f6220f17fc5b6e7300d0a55ac29f78f385ad89db276ccaa21693428fffa5919b" + }, + { + "path": "workplans/KEY-WP-0042-online-access-token-status.md", + "sha256": "6d7ff6dea31b251e261195fff97a2061d56579b5786629d95014f6d1cbe0f55e" + } + ], + "stdout": "[main 67a0869] Add opt-in issuer-owned access-token status and scoped revocation\n 18 files changed, 1230 insertions(+), 28 deletions(-)\n create mode 100644 docs/online-token-status.md\n create mode 100644 src/internal/config/token_status_test.go\n create mode 100644 src/internal/server/oidc/token_status.go\n create mode 100644 src/internal/server/oidc/token_status_discovery_test.go\n create mode 100644 src/internal/server/oidc/token_status_http.go\n create mode 100644 src/internal/server/oidc/token_status_internal_test.go\n create mode 100644 src/internal/server/oidc/token_status_test.go\n create mode 100644 src/internal/server/oidc/token_status_transport_test.go\n create mode 100644 workplans/KEY-WP-0042-online-access-token-status.md\n", + "stderr": "{\"blocked_blob_ids\": [], \"scanned_blobs\": 18, \"scanner_sha256\": \"88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\", \"scanner_version\": \"8.30.1\", \"scope\": \"changed-staged-blobs\", \"status\": \"clean\"}\n" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-inputs.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-inputs.json new file mode 100644 index 0000000..94ecad0 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-publication/publication-inputs.json @@ -0,0 +1,79 @@ +{ + "owner": "key-cape", + "base_revision": "d803e9282a47b49daf930fb905312fa5104c0ace", + "message": "Add opt-in issuer-owned access-token status and scoped revocation", + "files": [ + { + "path": "SCOPE.md", + "sha256": "a80e80ba2e5279ba50232e6b3f57a5bdd14866e584d2d055dfb09e98a4c24ce0" + }, + { + "path": "docs/online-token-status.md", + "sha256": "f5e41b47500b15c80b2da6cbe37a9e64bcbde1c072750934cf74464a19488925" + }, + { + "path": "docs/operations.md", + "sha256": "3659362e3c7f8720e52cc8dc7c67a6295ac26462f383c68c06c120fac6e6e472" + }, + { + "path": "src/cmd/keycape/main.go", + "sha256": "2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d" + }, + { + "path": "src/internal/config/config.go", + "sha256": "19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683" + }, + { + "path": "src/internal/config/token_status_test.go", + "sha256": "b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5" + }, + { + "path": "src/internal/config/validate.go", + "sha256": "2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405" + }, + { + "path": "src/internal/server/oidc/discovery.go", + "sha256": "00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5" + }, + { + "path": "src/internal/server/oidc/token.go", + "sha256": "304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f" + }, + { + "path": "src/internal/server/oidc/token_status.go", + "sha256": "d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3" + }, + { + "path": "src/internal/server/oidc/token_status_discovery_test.go", + "sha256": "27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031" + }, + { + "path": "src/internal/server/oidc/token_status_http.go", + "sha256": "a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97" + }, + { + "path": "src/internal/server/oidc/token_status_internal_test.go", + "sha256": "3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53" + }, + { + "path": "src/internal/server/oidc/token_status_test.go", + "sha256": "e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90" + }, + { + "path": "src/internal/server/oidc/token_status_transport_test.go", + "sha256": "3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347" + }, + { + "path": "src/internal/server/oidc/userinfo.go", + "sha256": "f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f" + }, + { + "path": "workplans/ADHOC-2026-09-27.md", + "sha256": "f6220f17fc5b6e7300d0a55ac29f78f385ad89db276ccaa21693428fffa5919b" + }, + { + "path": "workplans/KEY-WP-0042-online-access-token-status.md", + "sha256": "6d7ff6dea31b251e261195fff97a2061d56579b5786629d95014f6d1cbe0f55e" + } + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-readback.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-readback.json new file mode 100644 index 0000000..7812795 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-readback.json @@ -0,0 +1,30 @@ +{ + "workplan": { + "record_id": "KEY-WP-0042", + "uuid": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38", + "status": "proposed" + }, + "tasks": [ + { + "record_id": "KEY-WP-0042-T01", + "uuid": "d8af8e77-f6e5-5d0d-8b44-794af3dee5cf", + "status": "progress" + }, + { + "record_id": "KEY-WP-0042-T02", + "uuid": "2cade51f-d25a-5719-96b4-b57d50c1b97d", + "status": "done" + }, + { + "record_id": "KEY-WP-0042-T03", + "uuid": "a5539bb0-2501-5774-a7d8-acddfb0938ab", + "status": "todo" + } + ], + "task_rows_exact": true, + "existing_historical_binding_preserved": "7fa0bfc7-0772-57c7-8d6f-641615cab82a", + "existing_historical_status": "done", + "primary_projection_commit": "git:67a0869c1ee40c9e00a60cb7c1d828734d5d3d32", + "independent_owner_assent": false, + "native_admission": false +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-source.patch b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-source.patch new file mode 100644 index 0000000..97f47cf --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/owner-source.patch @@ -0,0 +1,1469 @@ +diff --git a/SCOPE.md b/SCOPE.md +--- a/SCOPE.md ++++ b/SCOPE.md +@@ -42,8 +42,10 @@ + + - Client registrations, service secrets and the signing key are loaded at startup. + Login, authorization and handoff state are process-local. There is no shared +- session store, general refresh-token flow, token introspection/revocation API, +- or automatic signing-key/client-secret rotation service. Logout clears the ++ session store, general refresh-token flow, or automatic signing-key/client-secret ++ rotation service. The default profile remains expiry-only. Proposed KEY-WP-0042 ++ adds an explicitly enabled, singleton, hash-only online access-token status ++ profile; see `docs/online-token-status.md` for its scope and admission limits. Logout clears the + local KeyCape session, not every upstream or downstream session/token. + - The optional tenant-engine `tenant_roles` adapter is wired through a + `tenantEngine` config block and is off unless `baseURL` is set (KEY-WP-0024). +diff --git a/docs/online-token-status.md b/docs/online-token-status.md +--- /dev/null ++++ b/docs/online-token-status.md +@@ -0,0 +1,119 @@ ++# Optional online access-token status ++ ++Source proposal under KEY-WP-0042, driven by CUST-WP-0080 and existing ++NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09. Independent issuer/consumer ++contract review and native admission remain required. Existing deployments are ++unchanged. This extends the bounded issuer; it does not introduce a broker, ++approval store, PDP, or a second application-local token registry. ++ ++The legacy profile authenticates self-contained JWTs until expiry. That does ++not establish that an operation's initiating token is still unrevoked. The ++optional profile adds issuer-owned current access-token status using ++[RFC 7662](https://www.rfc-editor.org/rfc/rfc7662.html) introspection and ++[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009.html) revocation. It supports ++access tokens only, without refresh tokens or token exchange. ++ ++## Enablement and lifecycle ++ ++Owner-admitted configuration opts in explicitly: ++ ++```yaml ++tokenStatus: ++ enabled: true ++ maxEntries: 10000 ++``` ++ ++The issuer must use HTTPS at its existing trusted ingress. The server's internal ++HTTP listener and reverse-proxy topology are unchanged; direct HTTP exposure is ++not admitted by an HTTPS issuer string. Omitted configuration keeps the existing ++expiry-only token/UserInfo contract and advertises no new endpoints. Enabling ++status registers `/introspect` and `/revoke`, advertises their implemented client ++authentication methods, and connects the same store to issuance and UserInfo. ++ ++Human PKCE and service grants add a cryptographically random `jti` to each access ++token. ID-token claims and existing assurance/tenant provenance remain unchanged. ++Distinct grants in the same second have distinct bearer tokens: a new grant can ++never remint a revoked token. A successful response is emitted only after the ++token hash, expiry and current registration commitment have been recorded. No ++bearer, client secret or identity/group snapshot is retained in the status store. ++ ++The store is bounded to the configured count (default 10000, maximum 1000000). ++Live records and revocation tombstones are never evicted to admit another grant; ++capacity exhaustion refuses issuance with a fixed 503. Expired entries are ++collected when admitting a new token. Capacity and per-client ingress rate limits ++need workload-representative admission: this is not a throughput/availability ++claim or protection against an authorized client filling the bound. ++ ++This profile uses KeyCape's existing **single-instance topology**. Status is ++process-local. Restart, deployment, rollback or store loss makes every earlier ++token inactive at online endpoints, even if its JWT signature/expiry still ++validates offline. Consumers must reauthenticate; there is no recovery replay ++or token-state import that can resurrect revoked authority. Multiple replicas ++are unsupported. Native admission must include this interruption and the ++measured reauthentication/recovery procedure. A persisted/shared store is not ++introduced here. ++ ++## Introspection and revocation ++ ++Both endpoints accept POST form bodies only, bound to 64 KiB; token values are ++bound to 32 KiB. Query parameters, duplicate form or authentication fields, ++unsupported content types and malformed authentication refuse without echoing ++input. Responses use `Cache-Control: no-store` and `Pragma: no-cache`. ++ ++Introspection requires the existing registered confidential client's ++`client_secret_basic` authentication. Only that client's own issued tokens can ++be inspected. A separate resource audience is returned as `aud`, not confused ++with the issuing `client_id`. Cross-client resource-server delegation needs a ++separately reviewed authorization contract and is not implicitly granted by a ++matching audience string. Public clients cannot enumerate token status. ++ ++An active response means the exact access token was issued in this process, ++has not expired or been revoked, still matches its current complete registration, ++and passes current issuer/signature/purpose validation. For humans, directory ++lookup must succeed and the current account must be enabled and not suspended. ++Status is checked again after directory lookup. Service subjects remain bound ++to their current registration. Active output contains only standard access-token ++metadata; inactive output contains only `active: false`. Unknown tokens, ID ++tokens, altered tokens, another client's tokens and expired/revoked tokens all ++return that same inactive response to an authenticated introspector. ++ ++Revocation authenticates confidential clients using the same method. A registered ++public client may revoke its own token using `client_id` plus possession of that ++bearer; public-client introspection remains unavailable. Revocation only affects ++the exact token issued to that client. Unknown, other-client, expired and already ++revoked tokens all produce the same empty 200. No token existence is disclosed. ++Token-type hints do not bypass the actual access-token lookup. Removal or changes ++to a registration invalidate online status; the commitment explicitly includes ++the secret digest and internal token lifetime omitted from public Client JSON. ++ ++When enabled, actual UserInfo consults the same store before lookup and again ++after it. Revocation therefore affects that existing endpoint immediately on its ++next check. Ordinary login/logout and upstream Authelia sessions are distinct: ++neither browser logout nor deleting a local cookie is represented as global ++JWT revocation. ++ ++## Consumer contract and remaining acceptance ++ ++Online status establishes current access-token status, not permission ++for an application resource or privileged effect. Consumers still independently ++validate the actual signed token, admitted issuer/key/audience/type, subject, ++tenant, principal type, assurance and freshness. The authenticated introspection ++response must refer to that exact token/client. Obtain a new current response ++before each protected operation; unavailable, malformed, inactive or ambiguous ++responses refuse. A cached positive is not a current-session check. ++ ++Access Engine retains resource/action decisions, Approval Engine exact one-shot ++approval, Warden admission and owner adapters execution. Bind their existing ++descriptor, case, transaction and exact effect before dispatch. A separate new ++operation/inverse token must be acquired and revoked on every terminal path; ++renewed authentication cannot revive a consumed approval or replay a mutation. ++This issuer capability alone implements none of those consumer bindings. ++ ++Native completion still requires independent issuer/profile review, exact ++image/config/trust admission, bounded retained-token custody, real human/service ++issuance, private transport, current-token/revocation integration in each owner, ++all exit-path cleanup, restart/expiry/unavailable/refusal checks, ongoing ++HTTP/API/Git/SSH authorization and audit evidence. Offline consumers which never ++consult the endpoint retain their existing JWT validity; no estate-wide immediate ++revocation claim follows from source implementation. Disabling the feature is ++not an acceptable fallback for a consumer admitted under the online profile. +diff --git a/docs/operations.md b/docs/operations.md +--- a/docs/operations.md ++++ b/docs/operations.md +@@ -20,7 +20,8 @@ + Consequences to plan for: + + - **A restart drops in-flight logins.** Anyone mid-login gets an error and must +- start again. Issued tokens are unaffected — they are self-contained JWTs and ++ start again. In the default profile issued tokens are unaffected — they are ++ self-contained JWTs and + stay valid until they expire. + - **Rolling deployments are single-instance rollovers**, so expect a brief window + where new logins fail. Draining (below) protects requests already in flight, not +@@ -89,7 +90,8 @@ + a same-`kid` swap as a breaking change for anyone caching aggressively. + + **Removing a client registration does not revoke tokens already issued to it.** +-There is no introspection or revocation endpoint, so an issued token stays valid ++In the default expiry-only profile there is no introspection or revocation ++endpoint, so an issued token stays valid + until it expires — 15 minutes by default, or the client's `tokenLifetime`. To cut + off a compromised client, remove the registration *and* wait out the lifetime, or + rotate the signing key if you cannot. +@@ -281,3 +283,12 @@ + `--operation remove` plus a restart is the rollback. A redirect URI that is not http(s) + skips the page. Every `auth_success` log line now carries `client_id`, `uid` and + `assurance`, and no token, code, email or one-time password. ++ ++## Proposed online token-status profile ++ ++KEY-WP-0042 adds an explicitly enabled singleton profile. It makes prior tokens ++inactive at online endpoints after restart and keeps revoked-token tombstones ++until expiry. It does not revoke offline consumers or upstream/browser sessions. ++Read [the complete lifecycle and admission contract](online-token-status.md) ++before proposing activation; no image/config rollout or consumer admission is ++established by source tests. Existing deployed configuration remains unchanged. +diff --git a/src/cmd/keycape/main.go b/src/cmd/keycape/main.go +--- a/src/cmd/keycape/main.go ++++ b/src/cmd/keycape/main.go +@@ -149,6 +149,24 @@ + // 12. Register HTTP handlers. + // ----------------------------------------------------------------- + mux := http.NewServeMux() ++ var tokenStatus *oidc.TokenStatusStore ++ var introspectionEndpoint, revocationEndpoint string ++ if cfg.TokenStatus.Enabled { ++ limit := cfg.TokenStatus.MaxEntries ++ if limit == 0 { ++ limit = 10000 ++ } ++ var err error ++ tokenStatus, err = oidc.NewTokenStatusStore(limit) ++ if err != nil { ++ log.Error().Msg("invalid token-status configuration") ++ os.Exit(1) ++ } ++ status := &oidc.TokenStatusHandler{Tokens: tokenStatus, ClientConfig: clients, Users: lldapAdapter, SigningKey: &privateKey.PublicKey, Issuer: issuer} ++ mux.HandleFunc("/introspect", status.Introspect) ++ mux.HandleFunc("/revoke", status.Revoke) ++ introspectionEndpoint, revocationEndpoint = issuer+"/introspect", issuer+"/revoke" ++ } + + // Discovery. + mux.Handle("/.well-known/openid-configuration", oidc.NewDiscoveryHandler(oidc.DiscoveryConfig{ +@@ -158,6 +176,8 @@ + JWKSUri: issuer + "/jwks", + UserinfoEndpoint: issuer + "/userinfo", + EndSessionEndpoint: issuer + "/logout", ++ IntrospectionEndpoint: introspectionEndpoint, ++ RevocationEndpoint: revocationEndpoint, + Clients: clients, + })) + +@@ -218,15 +238,18 @@ + // fails open, so a configured-but-unreachable cache omits the claim + // rather than failing issuance. + TenantEngine: buildTenantEngine(cfg.TenantEngine), ++ Tokens: tokenStatus, + } + mux.Handle("/token", enforcement.Middleware(tokenHandler)) + + // Userinfo handler. + mux.Handle("/userinfo", &oidc.UserinfoHandler{ +- Users: lldapAdapter, +- SigningKey: &privateKey.PublicKey, +- Issuer: issuer, +- Emitter: emitter, ++ Users: lldapAdapter, ++ SigningKey: &privateKey.PublicKey, ++ Issuer: issuer, ++ Emitter: emitter, ++ Tokens: tokenStatus, ++ ClientConfig: clients, + }) + + // Healthz. +diff --git a/src/internal/config/config.go b/src/internal/config/config.go +--- a/src/internal/config/config.go ++++ b/src/internal/config/config.go +@@ -30,6 +30,14 @@ + Clients []ClientConfig `yaml:"clients"` + Environment string `yaml:"environment"` + TenantEngine TenantEngineConfig `yaml:"tenantEngine,omitempty"` ++ TokenStatus TokenStatusConfig `yaml:"tokenStatus,omitempty"` ++} ++ ++// TokenStatusConfig opts a singleton issuer into online access-token status. ++// Existing deployment configuration remains expiry-only until owner admission. ++type TokenStatusConfig struct { ++ Enabled bool `yaml:"enabled"` ++ MaxEntries int `yaml:"maxEntries,omitempty"` + } + + // TenantEngineConfig configures the optional tenant_roles cache claim. +diff --git a/src/internal/config/token_status_test.go b/src/internal/config/token_status_test.go +--- /dev/null ++++ b/src/internal/config/token_status_test.go +@@ -0,0 +1,41 @@ ++package config_test ++ ++import ( ++ "strings" ++ "testing" ++ ++ "keycape/internal/config" ++) ++ ++func TestTokenStatusConfigIsExplicitBoundedAndHTTPS(t *testing.T) { ++ for _, kind := range []string{"legacy", "enabled-default", "enabled-bounded", "http", "userinfo-url", "query", "too-large", "negative", "disabled-capacity"} { ++ t.Run(kind, func(t *testing.T) { ++ cfg := validConfig("unused-public-test-key-path") ++ cfg.TokenStatus.Enabled = kind != "legacy" && kind != "disabled-capacity" ++ switch kind { ++ case "enabled-bounded": ++ cfg.TokenStatus.MaxEntries = 128 ++ case "http": ++ cfg.Issuer = "http://auth.example.com" ++ case "userinfo-url": ++ cfg.Issuer = "https://name@auth.example.com" ++ case "query": ++ cfg.Issuer = "https://auth.example.com?query=present" ++ case "too-large": ++ cfg.TokenStatus.MaxEntries = 1000001 ++ case "negative": ++ cfg.TokenStatus.MaxEntries = -1 ++ case "disabled-capacity": ++ cfg.TokenStatus.MaxEntries = 128 ++ } ++ var statusError bool ++ for _, err := range config.ValidateConfig(cfg) { ++ statusError = statusError || strings.HasPrefix(err, "tokenStatus") ++ } ++ expected := kind != "legacy" && kind != "enabled-default" && kind != "enabled-bounded" ++ if statusError != expected { ++ t.Fatal("token-status configuration acceptance mismatch") ++ } ++ }) ++ } ++} +diff --git a/src/internal/config/validate.go b/src/internal/config/validate.go +--- a/src/internal/config/validate.go ++++ b/src/internal/config/validate.go +@@ -12,6 +12,17 @@ + // Called at startup — the server must exit 1 if any errors are returned. + func ValidateConfig(cfg *Config) []string { + var errs []string ++ if cfg.TokenStatus.Enabled { ++ u, err := url.Parse(cfg.Issuer) ++ if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" { ++ errs = append(errs, "tokenStatus: requires an HTTPS issuer without credentials, query or fragment") ++ } ++ if cfg.TokenStatus.MaxEntries < 0 || cfg.TokenStatus.MaxEntries > 1000000 { ++ errs = append(errs, "tokenStatus.maxEntries: must be between 1 and 1000000, or omitted for 10000") ++ } ++ } else if cfg.TokenStatus.MaxEntries != 0 { ++ errs = append(errs, "tokenStatus.maxEntries: set while token status is disabled") ++ } + for name, raw := range map[string]string{"accountPortalURL": cfg.AccountPortalURL, "browserLogoutURL": cfg.BrowserLogoutURL} { + if raw == "" { + continue +diff --git a/src/internal/server/oidc/discovery.go b/src/internal/server/oidc/discovery.go +--- a/src/internal/server/oidc/discovery.go ++++ b/src/internal/server/oidc/discovery.go +@@ -20,6 +20,8 @@ + JWKSUri string + UserinfoEndpoint string // optional, empty = not advertised + EndSessionEndpoint string // optional, empty = not advertised ++ IntrospectionEndpoint string // opt-in online access-token status ++ RevocationEndpoint string // opt-in access-token revocation + // Clients supplies the registered clients so scopes_supported reflects the + // scopes this deployment can actually grant, rather than a fixed list that + // omits every configured resource scope (KEY-WP-0017-T03). Nil or empty +@@ -31,22 +33,26 @@ + // Fields are ordered to match common OIDC implementations for readability. + // registration_endpoint is intentionally absent — no dynamic client registration. + type discoveryDocument struct { +- Issuer string `json:"issuer"` +- AuthorizationEndpoint string `json:"authorization_endpoint"` +- TokenEndpoint string `json:"token_endpoint"` +- JWKSUri string `json:"jwks_uri"` +- UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` +- EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` +- ResponseTypesSupported []string `json:"response_types_supported"` +- GrantTypesSupported []string `json:"grant_types_supported"` +- CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` +- IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` +- ScopesSupported []string `json:"scopes_supported"` +- TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` +- ClaimsSupported []string `json:"claims_supported"` +- SubjectTypesSupported []string `json:"subject_types_supported"` +- RequestParameterSupported bool `json:"request_parameter_supported"` +- ClaimsParameterSupported bool `json:"claims_parameter_supported"` ++ Issuer string `json:"issuer"` ++ AuthorizationEndpoint string `json:"authorization_endpoint"` ++ TokenEndpoint string `json:"token_endpoint"` ++ JWKSUri string `json:"jwks_uri"` ++ UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` ++ EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` ++ IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` ++ RevocationEndpoint string `json:"revocation_endpoint,omitempty"` ++ IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` ++ RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` ++ ResponseTypesSupported []string `json:"response_types_supported"` ++ GrantTypesSupported []string `json:"grant_types_supported"` ++ CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` ++ IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` ++ ScopesSupported []string `json:"scopes_supported"` ++ TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` ++ ClaimsSupported []string `json:"claims_supported"` ++ SubjectTypesSupported []string `json:"subject_types_supported"` ++ RequestParameterSupported bool `json:"request_parameter_supported"` ++ ClaimsParameterSupported bool `json:"claims_parameter_supported"` + } + + // discoveryHandler implements http.Handler for GET /.well-known/openid-configuration. +@@ -84,6 +90,15 @@ + SubjectTypesSupported: []string{"public"}, + RequestParameterSupported: false, + ClaimsParameterSupported: false, ++ } ++ if cfg.IntrospectionEndpoint != "" { ++ d.IntrospectionEndpoint = cfg.IntrospectionEndpoint ++ d.IntrospectionEndpointAuthMethodsSupported = []string{"client_secret_basic"} ++ d.ClaimsSupported = append(d.ClaimsSupported, "jti") ++ } ++ if cfg.RevocationEndpoint != "" { ++ d.RevocationEndpoint = cfg.RevocationEndpoint ++ d.RevocationEndpointAuthMethodsSupported = []string{"client_secret_basic", "none"} + } + + b, err := json.Marshal(d) +diff --git a/src/internal/server/oidc/token.go b/src/internal/server/oidc/token.go +--- a/src/internal/server/oidc/token.go ++++ b/src/internal/server/oidc/token.go +@@ -32,6 +32,9 @@ + // TenantEngine sources the optional tenant_roles claim (KEY-WP-0005-T02). + // Nil disables it entirely -- token issuance never depends on it. + TenantEngine *tenantengine.Client ++ // Tokens enables issuer-owned online token status; nil retains the legacy ++ // expiry-only profile. Enabling it requires explicit deployment admission. ++ Tokens *TokenStatusStore + } + + // tokenResponse is the JSON body returned on a successful token exchange. +@@ -233,10 +236,24 @@ + claims["aud"] = audience + } + claims["scope"] = strings.Join(sess.Scopes, " ") ++ if h.Tokens != nil { ++ id := make([]byte, 32) ++ if _, err := rand.Read(id); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } ++ claims["jti"] = base64.RawURLEncoding.EncodeToString(id) ++ } + accessToken, err := buildJWT(claims, kid, h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return ++ } ++ if h.Tokens != nil { ++ if err := h.Tokens.register(accessToken, client, time.Unix(exp.Unix(), 0), now); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } + } + + // 8. Build response. The session was already consumed at lookup, so no +@@ -357,10 +374,24 @@ + if roles, ok := h.TenantEngine.Roles(ctx, client.Tenant); ok { + claims["tenant_roles"] = roles + } ++ if h.Tokens != nil { ++ id := make([]byte, 32) ++ if _, err := rand.Read(id); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } ++ claims["jti"] = base64.RawURLEncoding.EncodeToString(id) ++ } + jwtToken, err := buildJWT(claims, "key-1", h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return ++ } ++ if h.Tokens != nil { ++ if err := h.Tokens.register(jwtToken, client, time.Unix(now.Add(tokenLifetime).Unix(), 0), now); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } + } + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: now, EventType: telemetry.EventTokenIssued, ClientID: clientID, +diff --git a/src/internal/server/oidc/token_status.go b/src/internal/server/oidc/token_status.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status.go +@@ -0,0 +1,97 @@ ++package oidc ++ ++import ( ++ "crypto/sha256" ++ "encoding/json" ++ "errors" ++ "sync" ++ "time" ++ ++ "keycape/internal/domain" ++) ++ ++// TokenStatusStore is the optional single-process online access-token ledger. ++// It holds only token hashes and registration commitments, never bearer values. ++// Losing this store makes every previously issued token inactive to online ++// checks. It is not a browser session, approval, or operation authority store. ++type TokenStatusStore struct { ++ mu sync.Mutex ++ entries map[[32]byte]tokenStatus ++ limit int ++} ++ ++type tokenStatus struct { ++ clientID string ++ registration [32]byte ++ expires time.Time ++ revoked bool ++} ++ ++func NewTokenStatusStore(limit int) (*TokenStatusStore, error) { ++ if limit < 1 || limit > 1000000 { ++ return nil, errors.New("invalid token-status capacity") ++ } ++ return &TokenStatusStore{entries: make(map[[32]byte]tokenStatus), limit: limit}, nil ++} ++ ++func registrationCommitment(client *domain.Client) [32]byte { ++ // ClientSecret and TokenLifetime are deliberately omitted from Client JSON. ++ // Bind their commitments explicitly as well as all public registration data. ++ secret := sha256.Sum256([]byte(client.ClientSecret)) ++ raw, _ := json.Marshal(struct { ++ Client *domain.Client ++ Secret [32]byte ++ Lifetime time.Duration ++ }{client, secret, client.TokenLifetime}) ++ return sha256.Sum256(raw) ++} ++ ++func (s *TokenStatusStore) register(raw string, client *domain.Client, expires, now time.Time) error { ++ if s == nil || client == nil || raw == "" || len(raw) > 32768 || !now.Before(expires) { ++ return errors.New("token-status registration refused") ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ for key, entry := range s.entries { ++ if !now.Before(entry.expires) { ++ delete(s.entries, key) ++ } ++ } ++ key := sha256.Sum256([]byte(raw)) ++ if _, exists := s.entries[key]; exists || len(s.entries) >= s.limit { ++ return errors.New("token-status registration refused") ++ } ++ s.entries[key] = tokenStatus{clientID: client.ClientID, registration: registrationCommitment(client), expires: expires} ++ return nil ++} ++ ++func (s *TokenStatusStore) active(raw string, clients map[string]*domain.Client, now time.Time) (string, bool) { ++ if s == nil { ++ return "", false ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ entry, ok := s.entries[sha256.Sum256([]byte(raw))] ++ if !ok || entry.revoked || !now.Before(entry.expires) { ++ return "", false ++ } ++ client := clients[entry.clientID] ++ if client == nil || client.ClientID != entry.clientID || registrationCommitment(client) != entry.registration { ++ return "", false ++ } ++ return entry.clientID, true ++} ++ ++func (s *TokenStatusStore) revoke(raw, clientID string) { ++ if s == nil { ++ return ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ key := sha256.Sum256([]byte(raw)) ++ if entry, ok := s.entries[key]; ok && entry.clientID == clientID { ++ // Retain the tombstone until expiry: a retry can never reactivate it. ++ entry.revoked = true ++ s.entries[key] = entry ++ } ++} +diff --git a/src/internal/server/oidc/token_status_discovery_test.go b/src/internal/server/oidc/token_status_discovery_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_discovery_test.go +@@ -0,0 +1,30 @@ ++package oidc_test ++ ++import ( ++ "encoding/json" ++ "net/http" ++ "net/http/httptest" ++ "testing" ++ ++ "keycape/internal/server/oidc" ++) ++ ++func TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile(t *testing.T) { ++ for _, enabled := range []bool{false, true} { ++ cfg := oidc.DiscoveryConfig{Issuer: "https://issuer.example"} ++ if enabled { ++ cfg.IntrospectionEndpoint, cfg.RevocationEndpoint = cfg.Issuer+"/introspect", cfg.Issuer+"/revoke" ++ } ++ w := httptest.NewRecorder() ++ oidc.NewDiscoveryHandler(cfg).ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/.well-known/openid-configuration", nil)) ++ var doc map[string]interface{} ++ if json.Unmarshal(w.Body.Bytes(), &doc) != nil { ++ t.Fatal("discovery is invalid JSON") ++ } ++ for _, name := range []string{"introspection_endpoint", "revocation_endpoint", "introspection_endpoint_auth_methods_supported", "revocation_endpoint_auth_methods_supported"} { ++ if _, exists := doc[name]; exists != enabled { ++ t.Fatal("unimplemented token status advertised") ++ } ++ } ++ } ++} +diff --git a/src/internal/server/oidc/token_status_http.go b/src/internal/server/oidc/token_status_http.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_http.go +@@ -0,0 +1,145 @@ ++package oidc ++ ++import ( ++ "crypto/rsa" ++ "encoding/json" ++ "mime" ++ "net/http" ++ "time" ++ ++ "keycape/internal/domain" ++) ++ ++// TokenStatusHandler serves the optional issuer-owned RFC 7662 introspection ++// and RFC 7009 revocation endpoints. Introspection authenticates the issuing ++// confidential client; this profile does not delegate cross-client token reads. ++type TokenStatusHandler struct { ++ Tokens *TokenStatusStore ++ ClientConfig map[string]*domain.Client ++ Users domain.UserRepository ++ SigningKey *rsa.PublicKey ++ Issuer string ++} ++ ++func statusError(w http.ResponseWriter, status int, code string) { ++ w.Header().Set("Content-Type", "application/json") ++ w.Header().Set("Cache-Control", "no-store") ++ w.Header().Set("Pragma", "no-cache") ++ w.WriteHeader(status) ++ _ = json.NewEncoder(w).Encode(map[string]string{"error": code}) ++} ++ ++func (h *TokenStatusHandler) parse(w http.ResponseWriter, r *http.Request, introspection bool) (*domain.Client, string, bool) { ++ w.Header().Set("Cache-Control", "no-store") ++ w.Header().Set("Pragma", "no-cache") ++ if r.Method != http.MethodPost { ++ w.Header().Set("Allow", "POST") ++ statusError(w, http.StatusMethodNotAllowed, "invalid_request") ++ return nil, "", false ++ } ++ if h.Tokens == nil || h.SigningKey == nil || h.Issuer == "" { ++ statusError(w, http.StatusServiceUnavailable, "temporarily_unavailable") ++ return nil, "", false ++ } ++ media, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ++ if err != nil || media != "application/x-www-form-urlencoded" || r.URL.RawQuery != "" || len(r.Header.Values("Authorization")) > 1 { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ r.Body = http.MaxBytesReader(w, r.Body, 65536) ++ if err := r.ParseForm(); err != nil { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ for _, values := range r.PostForm { ++ if len(values) != 1 { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ } ++ raw := r.PostForm.Get("token") ++ if raw == "" || len(raw) > 32768 { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ id, secret, basic := basicClientCredentials(r) ++ formID := r.PostForm.Get("client_id") ++ if !basic { ++ id = formID ++ } ++ client := h.ClientConfig[id] ++ valid := client != nil && client.ClientID == id && !r.PostForm.Has("client_secret") ++ if valid && client.ClientType == "confidential" { ++ valid = basic && (formID == "" || formID == id) && client.ClientSecret != "" && secretsEqual(secret, client.ClientSecret) ++ } else { ++ // Public-client revocation requires possession of its own bearer token. ++ // Public clients can never enumerate token status via introspection. ++ valid = valid && !introspection && client.ClientType == "public" && !basic && r.Header.Get("Authorization") == "" ++ } ++ if !valid { ++ w.Header().Set("WWW-Authenticate", `Basic realm="keycape-token-status"`) ++ statusError(w, http.StatusUnauthorized, "invalid_client") ++ return nil, "", false ++ } ++ return client, raw, true ++} ++ ++func (h *TokenStatusHandler) Introspect(w http.ResponseWriter, r *http.Request) { ++ client, raw, ok := h.parse(w, r, true) ++ if !ok { ++ return ++ } ++ response := map[string]interface{}{"active": false} ++ defer func() { ++ w.Header().Set("Content-Type", "application/json") ++ _ = json.NewEncoder(w).Encode(response) ++ }() ++ id, active := h.Tokens.active(raw, h.ClientConfig, time.Now()) ++ if !active || id != client.ClientID { ++ return ++ } ++ claims, err := validateAccessToken(raw, h.SigningKey, h.Issuer) ++ if err != nil { ++ return ++ } ++ sub, _ := claims["sub"].(string) ++ switch claims["principal_type"] { ++ case "human": ++ if h.Users == nil { ++ return ++ } ++ user, err := lookupUserBySubject(r.Context(), h.Users, claims, sub) ++ if err != nil || !user.Enabled || isSuspended(user) { ++ return ++ } ++ case "service": ++ if sub == "" || sub != client.ServiceSubject { ++ return ++ } ++ default: ++ return ++ } ++ // Directory lookup may block. Recheck revocation and registration after it. ++ if current, active := h.Tokens.active(raw, h.ClientConfig, time.Now()); !active || current != id { ++ return ++ } ++ response["active"] = true ++ response["client_id"] = id ++ response["token_type"] = "Bearer" ++ for _, field := range []string{"iss", "sub", "aud", "scope", "iat", "exp", "jti"} { ++ if value, present := claims[field]; present { ++ response[field] = value ++ } ++ } ++} ++ ++func (h *TokenStatusHandler) Revoke(w http.ResponseWriter, r *http.Request) { ++ client, raw, ok := h.parse(w, r, false) ++ if !ok { ++ return ++ } ++ // Unknown, expired, ID and other-client tokens receive the same empty ++ // success as an already-revoked token; no existence information leaks. ++ h.Tokens.revoke(raw, client.ClientID) ++ w.WriteHeader(http.StatusOK) ++} +diff --git a/src/internal/server/oidc/token_status_internal_test.go b/src/internal/server/oidc/token_status_internal_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_internal_test.go +@@ -0,0 +1,66 @@ ++package oidc ++ ++import ( ++ "fmt" ++ "sync" ++ "testing" ++ "time" ++ ++ "keycape/internal/domain" ++) ++ ++func TestTokenStatusExactExpiryCapacityAndTombstones(t *testing.T) { ++ client := &domain.Client{ClientID: "synthetic-client", ClientType: "public"} ++ clients := map[string]*domain.Client{client.ClientID: client} ++ store, _ := NewTokenStatusStore(1) ++ now := time.Unix(1700000000, 0) ++ end := now.Add(time.Second) ++ if store.register("synthetic-first", client, end, now) != nil { ++ t.Fatal("register refused") ++ } ++ if _, active := store.active("synthetic-first", clients, end); active { ++ t.Fatal("expiry equality accepted") ++ } ++ store.revoke("synthetic-first", client.ClientID) ++ if store.register("synthetic-first", client, end, now) == nil { ++ t.Fatal("revoked token reactivated") ++ } ++ if store.register("synthetic-second", client, end.Add(time.Second), end) != nil { ++ t.Fatal("expired tombstone not collected") ++ } ++ if _, active := store.active("synthetic-second", clients, end); !active { ++ t.Fatal("fresh token refused") ++ } ++ for _, capacity := range []int{-1, 0, 1000001} { ++ if _, err := NewTokenStatusStore(capacity); err == nil { ++ t.Fatal("unbounded capacity accepted") ++ } ++ } ++} ++ ++func TestTokenStatusConcurrentIssueReadAndRevoke(t *testing.T) { ++ store, _ := NewTokenStatusStore(128) ++ client := &domain.Client{ClientID: "synthetic-client", ClientType: "public"} ++ clients := map[string]*domain.Client{client.ClientID: client} ++ now := time.Now() ++ var wg sync.WaitGroup ++ for i := 0; i < 64; i++ { ++ wg.Add(1) ++ go func(i int) { ++ defer wg.Done() ++ raw := fmt.Sprintf("synthetic-token-%d", i) ++ if store.register(raw, client, now.Add(time.Minute), now) != nil { ++ t.Error("concurrent registration failed") ++ return ++ } ++ if _, active := store.active(raw, clients, now); !active { ++ t.Error("concurrent current token refused") ++ } ++ store.revoke(raw, client.ClientID) ++ if _, active := store.active(raw, clients, now); active { ++ t.Error("concurrent revoked token accepted") ++ } ++ }(i) ++ } ++ wg.Wait() ++} +diff --git a/src/internal/server/oidc/token_status_test.go b/src/internal/server/oidc/token_status_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_test.go +@@ -0,0 +1,379 @@ ++package oidc_test ++ ++import ( ++ "context" ++ "encoding/json" ++ "io" ++ "net/http" ++ "net/http/httptest" ++ "net/url" ++ "strings" ++ "testing" ++ "time" ++ ++ "keycape/internal/domain" ++ "keycape/internal/server/oidc" ++ "keycape/internal/server/telemetry" ++) ++ ++type statusFixture struct { ++ token *oidc.TokenHandler ++ status *oidc.TokenStatusHandler ++ userinfo *oidc.UserinfoHandler ++ users *mockUserRepo ++} ++ ++func newStatusFixture(t *testing.T, capacity int) *statusFixture { ++ t.Helper() ++ users := &mockUserRepo{users: map[string]*domain.User{"alice": aliceUser()}} ++ tokens, err := oidc.NewTokenStatusStore(capacity) ++ if err != nil { ++ t.Fatal("store creation failed") ++ } ++ h, key := newTokenHandler(t, oidc.NewSessionStore(), users) ++ h.Tokens = tokens ++ h.ClientConfig["test-client"].ClientType = "confidential" ++ h.ClientConfig["test-client"].ClientSecret = "synthetic-status-only" ++ h.ClientConfig["other"] = &domain.Client{ClientID: "other", ClientType: "confidential", ClientSecret: "synthetic-other-only"} ++ return &statusFixture{token: h, users: users, ++ status: &oidc.TokenStatusHandler{Tokens: tokens, ClientConfig: h.ClientConfig, Users: users, SigningKey: &key.PublicKey, Issuer: h.Issuer}, ++ userinfo: &oidc.UserinfoHandler{Tokens: tokens, ClientConfig: h.ClientConfig, Users: users, SigningKey: &key.PublicKey, Issuer: h.Issuer, Emitter: telemetry.NoopEmitter{}}, ++ } ++} ++ ++func (f *statusFixture) issue(t *testing.T) (string, string, int) { ++ t.Helper() ++ verifier, _ := makeVerifierAndChallenge() ++ code := seededSession(f.token.Sessions, verifier) ++ req := tokenRequest(url.Values{"grant_type": {"authorization_code"}, "client_id": {"test-client"}, "code": {code}, "code_verifier": {verifier}}) ++ if f.token.ClientConfig["test-client"].ClientType == "confidential" { ++ req.SetBasicAuth(url.QueryEscape("test-client"), url.QueryEscape(f.token.ClientConfig["test-client"].ClientSecret)) ++ } ++ w := httptest.NewRecorder() ++ f.token.ServeHTTP(w, req) ++ if w.Code != http.StatusOK { ++ return "", "", w.Code ++ } ++ var body struct { ++ Access string `json:"access_token"` ++ ID string `json:"id_token"` ++ } ++ if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Access == "" || body.ID == "" { ++ t.Fatal("invalid token response") ++ } ++ return body.Access, body.ID, w.Code ++} ++ ++func (f *statusFixture) request(t *testing.T, raw, client, secret string, revoke bool, mutate func(*http.Request)) *httptest.ResponseRecorder { ++ t.Helper() ++ form := url.Values{"token": {raw}} ++ if secret == "" { ++ form.Set("client_id", client) ++ } ++ r := httptest.NewRequest(http.MethodPost, "/introspect", strings.NewReader(form.Encode())) ++ r.Header.Set("Content-Type", "application/x-www-form-urlencoded") ++ if secret != "" { ++ r.SetBasicAuth(url.QueryEscape(client), url.QueryEscape(secret)) ++ } ++ if mutate != nil { ++ mutate(r) ++ } ++ w := httptest.NewRecorder() ++ if revoke { ++ f.status.Revoke(w, r) ++ } else { ++ f.status.Introspect(w, r) ++ } ++ if w.Header().Get("Cache-Control") != "no-store" || w.Header().Get("Pragma") != "no-cache" { ++ t.Fatal("missing private response controls") ++ } ++ return w ++} ++ ++func (f *statusFixture) introspect(t *testing.T, raw string) map[string]interface{} { ++ t.Helper() ++ w := f.request(t, raw, "test-client", f.token.ClientConfig["test-client"].ClientSecret, false, nil) ++ if w.Code != http.StatusOK { ++ t.Fatalf("introspection status %d", w.Code) ++ } ++ var body map[string]interface{} ++ if json.Unmarshal(w.Body.Bytes(), &body) != nil { ++ t.Fatal("invalid introspection response") ++ } ++ return body ++} ++ ++func (f *statusFixture) userinfoStatus(raw string) int { ++ w := httptest.NewRecorder() ++ f.userinfo.ServeHTTP(w, userinfoRequest(raw)) ++ return w.Code ++} ++ ++func TestTokenStatusActualIssueRevokeAndFreshGrant(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ first, id, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatalf("issue status %d", code) ++ } ++ active := f.introspect(t, first) ++ if active["active"] != true || active["sub"] != "user-alice" || active["client_id"] != "test-client" || active["token_type"] != "Bearer" { ++ t.Fatal("wrong active-token metadata") ++ } ++ if _, present := parseJWTPayload(t, id)["jti"]; present { ++ t.Fatal("access-token lifecycle changed ID token") ++ } ++ if f.userinfoStatus(first) != http.StatusOK { ++ t.Fatal("issued token refused") ++ } ++ second, _, code := f.issue(t) ++ if code != http.StatusOK || first == second || active["jti"] == f.introspect(t, second)["jti"] { ++ t.Fatal("fresh grants must be distinct") ++ } ++ for i := 0; i < 2; i++ { ++ w := f.request(t, first, "test-client", "synthetic-status-only", true, nil) ++ if w.Code != http.StatusOK || w.Body.Len() != 0 { ++ t.Fatal("revocation must be empty idempotent success") ++ } ++ } ++ if got := f.introspect(t, first); got["active"] != false || len(got) != 1 { ++ t.Fatal("revoked metadata leaked or remained active") ++ } ++ if f.userinfoStatus(first) != http.StatusUnauthorized { ++ t.Fatal("revoked token still accepted by real UserInfo") ++ } ++ if f.introspect(t, second)["active"] != true || f.userinfoStatus(second) != http.StatusOK { ++ t.Fatal("revoking one grant affected another") ++ } ++ third, _, code := f.issue(t) ++ if code != http.StatusOK || f.introspect(t, third)["active"] != true || f.introspect(t, first)["active"] != false { ++ t.Fatal("fresh issuance reactivated a revoked grant") ++ } ++} ++ ++func TestTokenStatusNoCrossClientOrIDTokenAuthority(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, id, _ := f.issue(t) ++ for _, invalid := range []string{id, "unknown.synthetic.token", raw + "changed"} { ++ got := f.introspect(t, invalid) ++ if len(got) != 1 || got["active"] != false { ++ t.Fatal("non-access token disclosed metadata") ++ } ++ w := f.request(t, invalid, "test-client", "synthetic-status-only", true, nil) ++ if w.Code != http.StatusOK || w.Body.Len() != 0 { ++ t.Fatal("unknown revocation disclosed state") ++ } ++ } ++ w := f.request(t, raw, "other", "synthetic-other-only", false, nil) ++ if w.Code != http.StatusOK || strings.TrimSpace(w.Body.String()) != `{"active":false}` { ++ t.Fatal("other client read token") ++ } ++ w = f.request(t, raw, "other", "synthetic-other-only", true, nil) ++ if w.Code != http.StatusOK || f.introspect(t, raw)["active"] != true { ++ t.Fatal("other client revoked token") ++ } ++} ++ ++func TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken(t *testing.T) { ++ f := newStatusFixture(t, 1) ++ raw, _, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatal("first issuance failed") ++ } ++ if _, _, code = f.issue(t); code != http.StatusServiceUnavailable { ++ t.Fatalf("full store issue status %d", code) ++ } ++ if f.introspect(t, raw)["active"] != true { ++ t.Fatal("capacity evicted live authority") ++ } ++ f.request(t, raw, "test-client", "synthetic-status-only", true, nil) ++ if _, _, code = f.issue(t); code != http.StatusServiceUnavailable { ++ t.Fatal("revocation tombstone was evicted before expiry") ++ } ++} ++ ++func TestTokenStatusRestartAndUnavailableStoreRefuse(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ empty, err := oidc.NewTokenStatusStore(8) ++ if err != nil { ++ t.Fatal(err) ++ } ++ f.status.Tokens, f.userinfo.Tokens = empty, empty ++ if f.introspect(t, raw)["active"] != false || f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("restart resurrected unknown token") ++ } ++ f.status.Tokens = nil ++ if w := f.request(t, raw, "test-client", "synthetic-status-only", false, nil); w.Code != http.StatusServiceUnavailable { ++ t.Fatal("missing store allowed online status") ++ } ++ // Offline JWT validity remains a separate legacy contract. Do not claim ++ // that adding an endpoint revokes tokens in consumers that never consult it. ++ f.userinfo.Tokens = nil ++ if f.userinfoStatus(raw) != http.StatusOK { ++ t.Fatal("legacy profile changed") ++ } ++} ++ ++func TestTokenStatusRegistrationDriftAndDirectoryDisposition(t *testing.T) { ++ for _, change := range []string{"removed", "audience", "secret", "scopes", "lifetime", "suspended", "disabled", "user-removed"} { ++ t.Run(change, func(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ switch change { ++ case "removed": ++ delete(f.token.ClientConfig, "test-client") ++ case "audience": ++ f.token.ClientConfig["test-client"].Audience = "different-resource" ++ case "secret": ++ f.token.ClientConfig["test-client"].ClientSecret = "replacement-synthetic-only" ++ case "scopes": ++ f.token.ClientConfig["test-client"].AllowedScopes = []string{"openid"} ++ case "lifetime": ++ f.token.ClientConfig["test-client"].TokenLifetime = time.Minute ++ case "suspended": ++ f.users.users["alice"].Groups = []string{"netkingdom-suspended"} ++ case "disabled": ++ f.users.users["alice"].Enabled = false ++ case "user-removed": ++ delete(f.users.users, "alice") ++ } ++ if f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("UserInfo accepted changed authority") ++ } ++ if change != "removed" && f.introspect(t, raw)["active"] != false { ++ t.Fatal("introspection accepted changed authority") ++ } ++ }) ++ } ++} ++ ++func TestTokenStatusPublicClientMayRevokeButNotIntrospect(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ f.token.ClientConfig["test-client"].ClientType = "public" ++ f.token.ClientConfig["test-client"].ClientSecret = "" ++ raw, _, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatal("public PKCE issue failed") ++ } ++ if w := f.request(t, raw, "test-client", "", false, nil); w.Code != http.StatusUnauthorized { ++ t.Fatal("public client enumerated status") ++ } ++ if w := f.request(t, raw, "test-client", "", true, nil); w.Code != http.StatusOK { ++ t.Fatal("public bearer revocation failed") ++ } ++ if f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("public revocation not enforced") ++ } ++} ++ ++func TestTokenStatusServiceGrantIsCurrentAndDistinct(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ client := f.token.ClientConfig["test-client"] ++ client.GrantTypes, client.ServiceSubject, client.Tenant = []string{"client_credentials"}, "service:status-fixture", "tenant:fixture" ++ issue := func() string { ++ r := tokenRequest(url.Values{"grant_type": {"client_credentials"}}) ++ r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret)) ++ w := httptest.NewRecorder() ++ f.token.ServeHTTP(w, r) ++ if w.Code != http.StatusOK { ++ t.Fatalf("service issue status %d", w.Code) ++ } ++ var body struct { ++ Access string `json:"access_token"` ++ } ++ if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Access == "" { ++ t.Fatal("service response invalid") ++ } ++ return body.Access ++ } ++ first, second := issue(), issue() ++ if first == second || f.introspect(t, first)["active"] != true { ++ t.Fatal("service grants not distinct/current") ++ } ++ f.request(t, first, client.ClientID, client.ClientSecret, true, nil) ++ if f.introspect(t, first)["active"] != false || f.introspect(t, second)["active"] != true { ++ t.Fatal("service revocation not token-scoped") ++ } ++} ++ ++func TestTokenStatusRejectsMalformedOrUnauthenticatedRequests(t *testing.T) { ++ for _, kind := range []string{"get", "query", "json", "duplicate-token", "oversize", "wrong-secret", "missing-auth", "duplicate-auth", "body-secret", "mismatched-client"} { ++ t.Run(kind, func(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ mutate := func(r *http.Request) { ++ body := func(form url.Values) { r.Body = io.NopCloser(strings.NewReader(form.Encode())) } ++ switch kind { ++ case "get": ++ r.Method = http.MethodGet ++ case "query": ++ r.URL.RawQuery = "token=must-never-use-url" ++ case "json": ++ r.Header.Set("Content-Type", "application/json") ++ case "duplicate-token": ++ body(url.Values{"token": {raw, raw}}) ++ case "oversize": ++ body(url.Values{"token": {strings.Repeat("x", 65537)}}) ++ case "wrong-secret": ++ r.SetBasicAuth("test-client", "wrong-synthetic-secret") ++ case "missing-auth": ++ r.Header.Del("Authorization") ++ case "duplicate-auth": ++ r.Header.Add("Authorization", "Basic extra") ++ case "body-secret": ++ body(url.Values{"token": {raw}, "client_secret": {"unsupported"}}) ++ case "mismatched-client": ++ body(url.Values{"token": {raw}, "client_id": {"other"}}) ++ } ++ } ++ for _, revoke := range []bool{false, true} { ++ w := f.request(t, raw, "test-client", "synthetic-status-only", revoke, mutate) ++ if w.Code < 400 { ++ t.Fatal("malformed request accepted") ++ } ++ } ++ if f.introspect(t, raw)["active"] != true { ++ t.Fatal("refused request changed token") ++ } ++ }) ++ } ++} ++ ++type blockingStatusUsers struct { ++ *mockUserRepo ++ entered, release chan struct{} ++} ++ ++func (u *blockingStatusUsers) LookupUser(ctx context.Context, name string) (*domain.User, error) { ++ close(u.entered) ++ select { ++ case <-u.release: ++ return u.mockUserRepo.LookupUser(ctx, name) ++ case <-ctx.Done(): ++ return nil, ctx.Err() ++ } ++} ++ ++func TestTokenStatusRevocationDuringDirectoryLookupRefuses(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ users := &blockingStatusUsers{mockUserRepo: f.users, entered: make(chan struct{}), release: make(chan struct{})} ++ f.status.Users = users ++ done := make(chan *httptest.ResponseRecorder, 1) ++ go func() { done <- f.request(t, raw, "test-client", "synthetic-status-only", false, nil) }() ++ select { ++ case <-users.entered: ++ case <-time.After(3 * time.Second): ++ t.Fatal("directory lookup not entered") ++ } ++ f.request(t, raw, "test-client", "synthetic-status-only", true, nil) ++ close(users.release) ++ select { ++ case w := <-done: ++ if strings.TrimSpace(w.Body.String()) != `{"active":false}` { ++ t.Fatal("delayed lookup resurrected revoked token") ++ } ++ case <-time.After(3 * time.Second): ++ t.Fatal("introspection did not finish") ++ } ++} +diff --git a/src/internal/server/oidc/token_status_transport_test.go b/src/internal/server/oidc/token_status_transport_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_transport_test.go +@@ -0,0 +1,121 @@ ++package oidc_test ++ ++import ( ++ "encoding/json" ++ "io" ++ "net/http" ++ "net/http/httptest" ++ "net/url" ++ "strings" ++ "testing" ++ "time" ++) ++ ++func TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ f.token.ClientConfig["test-client"].ClientSecret = "synthetic + / colon: only" ++ mux := http.NewServeMux() ++ mux.Handle("/token", f.token) ++ mux.Handle("/userinfo", f.userinfo) ++ mux.HandleFunc("/introspect", f.status.Introspect) ++ mux.HandleFunc("/revoke", f.status.Revoke) ++ server := httptest.NewTLSServer(mux) ++ defer server.Close() ++ client := server.Client() ++ verifier, _ := makeVerifierAndChallenge() ++ code := seededSession(f.token.Sessions, verifier) ++ post := func(path string, form url.Values) (int, []byte) { ++ r, err := http.NewRequest(http.MethodPost, server.URL+path, strings.NewReader(form.Encode())) ++ if err != nil { ++ t.Fatal("request construction failed") ++ } ++ r.Header.Set("Content-Type", "application/x-www-form-urlencoded") ++ r.SetBasicAuth(url.QueryEscape("test-client"), url.QueryEscape(f.token.ClientConfig["test-client"].ClientSecret)) ++ resp, err := client.Do(r) ++ if err != nil { ++ t.Fatal("HTTPS fixture transport failed") ++ } ++ defer resp.Body.Close() ++ body, err := io.ReadAll(io.LimitReader(resp.Body, 65537)) ++ if err != nil || len(body) > 65536 { ++ t.Fatal("response read failed") ++ } ++ return resp.StatusCode, body ++ } ++ status, body := post("/token", url.Values{"grant_type": {"authorization_code"}, "client_id": {"test-client"}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {seededRedirectURI}}) ++ var tokens struct { ++ Access string `json:"access_token"` ++ } ++ if status != http.StatusOK || json.Unmarshal(body, &tokens) != nil || tokens.Access == "" { ++ t.Fatal("actual HTTPS PKCE exchange failed") ++ } ++ // A misleading type hint must not hide a supported access token. ++ form := url.Values{"token": {tokens.Access}, "token_type_hint": {"refresh_token"}} ++ status, body = post("/introspect", form) ++ var metadata map[string]interface{} ++ if status != http.StatusOK || json.Unmarshal(body, &metadata) != nil || metadata["active"] != true { ++ t.Fatal("actual HTTPS introspection failed") ++ } ++ if status, body = post("/revoke", form); status != http.StatusOK || len(body) != 0 { ++ t.Fatal("actual HTTPS revocation failed") ++ } ++ status, body = post("/introspect", form) ++ if status != http.StatusOK || strings.TrimSpace(string(body)) != `{"active":false}` { ++ t.Fatal("revocation did not affect HTTPS introspection") ++ } ++ r, _ := http.NewRequest(http.MethodGet, server.URL+"/userinfo", nil) ++ r.Header.Set("Authorization", "Bearer "+tokens.Access) ++ resp, err := client.Do(r) ++ if err != nil { ++ t.Fatal("HTTPS UserInfo failed") ++ } ++ defer resp.Body.Close() ++ if resp.StatusCode != http.StatusUnauthorized || resp.Header.Get("Cache-Control") != "no-store" { ++ t.Fatal("real UserInfo accepted or cached revoked bearer") ++ } ++} ++ ++func TestTokenStatusRevocationDuringUserInfoLookupRefuses(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ users := &blockingStatusUsers{mockUserRepo: f.users, entered: make(chan struct{}), release: make(chan struct{})} ++ f.userinfo.Users = users ++ done := make(chan int, 1) ++ go func() { done <- f.userinfoStatus(raw) }() ++ select { ++ case <-users.entered: ++ case <-time.After(3 * time.Second): ++ t.Fatal("UserInfo did not enter directory lookup") ++ } ++ f.request(t, raw, "test-client", "synthetic-status-only", true, nil) ++ close(users.release) ++ select { ++ case code := <-done: ++ if code != http.StatusUnauthorized { ++ t.Fatal("delayed UserInfo lookup resurrected revoked token") ++ } ++ case <-time.After(3 * time.Second): ++ t.Fatal("UserInfo did not finish") ++ } ++} ++ ++func TestTokenStatusLegacyGrantHasNoStatusClaim(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ f.token.Tokens = nil ++ raw, id, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatal("legacy issue failed") ++ } ++ for _, token := range []string{raw, id} { ++ if _, present := parseJWTPayload(t, token)["jti"]; present { ++ t.Fatal("disabled token contract changed") ++ } ++ } ++ if f.introspect(t, raw)["active"] != false || f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("unregistered legacy token gained online authority") ++ } ++ f.userinfo.Tokens = nil ++ if f.userinfoStatus(raw) != http.StatusOK { ++ t.Fatal("legacy token no longer accepted in legacy mode") ++ } ++} +diff --git a/src/internal/server/oidc/userinfo.go b/src/internal/server/oidc/userinfo.go +--- a/src/internal/server/oidc/userinfo.go ++++ b/src/internal/server/oidc/userinfo.go +@@ -22,15 +22,21 @@ + // the user, and returns claims that are consistent with those in the ID token + // for the same scope set. + type UserinfoHandler struct { +- Users domain.UserRepository +- SigningKey *rsa.PublicKey // used to verify the incoming access token +- Issuer string +- Emitter telemetry.Emitter ++ Users domain.UserRepository ++ SigningKey *rsa.PublicKey // used to verify the incoming access token ++ Issuer string ++ Emitter telemetry.Emitter ++ Tokens *TokenStatusStore ++ ClientConfig map[string]*domain.Client + } + + // ServeHTTP handles GET /userinfo. + func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() ++ if h.Tokens != nil { ++ w.Header().Set("Cache-Control", "no-store") ++ w.Header().Set("Pragma", "no-cache") ++ } + + // 1. Extract Bearer token. + tokenStr, ok := bearerToken(r) +@@ -45,6 +51,12 @@ + if err != nil { + http.Error(w, `{"error":"invalid_token","description":"token validation failed"}`, http.StatusUnauthorized) + return ++ } ++ if h.Tokens != nil { ++ if _, active := h.Tokens.active(tokenStr, h.ClientConfig, time.Now()); !active { ++ http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized) ++ return ++ } + } + + // 3. Extract sub claim (which is the username in our model). +@@ -67,6 +79,12 @@ + if isSuspended(user) { + http.Error(w, `{"error":"invalid_token","description":"subject is suspended"}`, http.StatusUnauthorized) + return ++ } ++ if h.Tokens != nil { ++ if _, active := h.Tokens.active(tokenStr, h.ClientConfig, time.Now()); !user.Enabled || !active { ++ http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized) ++ return ++ } + } + + // 5. Build response claims filtered by the scopes embedded in the token. +diff --git a/workplans/ADHOC-2026-09-27.md b/workplans/ADHOC-2026-09-27.md +--- a/workplans/ADHOC-2026-09-27.md ++++ b/workplans/ADHOC-2026-09-27.md +@@ -70,6 +70,7 @@ + id: ADHOC-2026-09-27-T02 + status: done + priority: high ++state_hub_task_id: "7fa0bfc7-0772-57c7-8d6f-641615cab82a" + ``` + + Rolling out T01 (image digest `sha256:248e449a...`) broke every KeyCape login, +diff --git a/workplans/KEY-WP-0042-online-access-token-status.md b/workplans/KEY-WP-0042-online-access-token-status.md +--- /dev/null ++++ b/workplans/KEY-WP-0042-online-access-token-status.md +@@ -0,0 +1,84 @@ ++--- ++id: KEY-WP-0042 ++type: workplan ++title: "Provide issuer-owned current access-token status and scoped revocation" ++domain: infotech ++repo: key-cape ++status: proposed ++owner: codex ++topic_slug: custodian ++created: "2026-10-18" ++updated: "2026-10-18" ++flavor: extension ++depends_on: [] ++state_hub_workstream_id: "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38" ++--- ++ ++CUST-WP-0080's current-operation/inverse requirement exposed a concrete missing ++issuer facility. KEY-WP-0025-T03 truthfully documents the expiry-only limit; ++KEY-WP-0038 owns Forgejo's existing client, not a token-liveness API. This is an ++explicit proposed scope extension in the existing issuer, not a silent change ++to that completed limit record. No new service or duplicate session authority. ++Consumer binding/authorization and native case acceptance stay in ++NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09; they are not copied here. ++ ++## Review the bounded issuer/profile extension ++ ++```task ++id: KEY-WP-0042-T01 ++status: progress ++priority: high ++state_hub_task_id: "d8af8e77-f6e5-5d0d-8b44-794af3dee5cf" ++``` ++ ++The reviewable contract is `docs/online-token-status.md`: opt-in RFC 7662/7009 ++access-token status; registered owning-client authentication; private hash-only ++bounded state; unique access-token jti; no ID-token change; singleton store-loss ++refusal; disabled legacy behavior explicit. Source preparation is authorized by ++the full Custodian implementation goal. Independent issuer/profile/consumer ++assent remains required before admission; no such assent is inferred here. ++ ++## Implement and verify the actual issuer paths ++ ++```task ++id: KEY-WP-0042-T02 ++status: done ++priority: high ++state_hub_task_id: "2cade51f-d25a-5719-96b4-b57d50c1b97d" ++``` ++ ++Wire the same issuer store to both actual grant types, UserInfo, introspection, ++revocation, configuration validation and implemented discovery. Verify distinct ++grants, exact expiry, replay/tombstones, capacity, malformed/unauthenticated and ++cross-client refusals, current registration and human disposition, revocation ++during directory lookup, process-store loss and absence of secret output. Keep ++the complete existing source suite, disabled profile and build passing. ++ ++## Admit and accept the issuer lifecycle at native scope ++ ++```task ++id: KEY-WP-0042-T03 ++status: todo ++priority: high ++state_hub_task_id: "a5539bb0-2501-5774-a7d8-acddfb0938ab" ++``` ++ ++After independent contract review, admit exact issuer image/config/profile, ++HTTPS ingress/client custody and measured capacity/headroom. Exercise real ++human PKCE and service tokens, actual endpoint authentication, revoked-token ++UserInfo refusal, expiry, unavailable dependency, restart/store loss and fresh ++reauthentication. Preserve old offline-token semantics and document every ++consumer's selected mode. Review disabling/rollback explicitly; never silently ++fall back to offline acceptance for online-profile consumers. Provide owner ++receipts to existing NK48/Apps consumer tasks and Audit source admission. ++ ++2026-10-18 source verification: all 659 test/subtest results in the complete ++race suite pass, with six preexisting native/external skips. The standalone ++binary passes 26 isolated process checks, including actual main/router/config, ++service issuance, capacity, revocation, process restart, fresh issuance and ++disabled-profile behavior; fixture diagnostics omit synthetic private inputs. ++The HTTPS human PKCE flow and revocation during directory lookups also pass. ++An isolated control omitting the revoked-state predicate fails seven intended ++checks. This closes source task T02 only. T01 independent issuer/profile review, ++T03 native admission and all existing consumer operation/effect bindings remain ++open. Evidence is retained by CUST-WP-0080 under keycape-token-status-r1. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/SCOPE.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/SCOPE.md new file mode 100644 index 0000000..ef9506b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/SCOPE.md @@ -0,0 +1,224 @@ +# SCOPE + +Reviewed 2026-09-10 against source revision `f247d35`. + +Review lineage, because "what changed since?" should have a usable answer. The +full capability review was 2026-09-09 against `5ae80d3` +([reassessment](history/2026-09-09-scope-reassessment.md)), superseding +2026-09-05 against `b989de4`. `7a73352` followed it by a minute — documentation +and workplans only — and was confirmed by reading the commit rather than by +report. Since then the tenant claim gained `tenant_source`, the approver client +was registered, and two guards were added that pin properties rather than +behaviours; each is described below. Read versus told, and reviewed versus merely +later, are distinctions this header is meant to preserve. + +## Purpose and boundary + +KeyCape is Go identity **Tooling** for NetKingdom's lightweight IAM deployment. +It runs its own OIDC-style issuer, uses Authelia for upstream browser +authentication, reads identities from LLDAP, and delegates factor checks to +privacyIDEA. It also provides caller authentication commands, directory snapshot +validation, and migration artifact generators. + +It issues identity, role, scope and assurance claims. Resource authorization +belongs to access-engine and consuming services; secret custody and OpenBao +policy/token enforcement belong to their platform owners. The implemented subset +supports NetKingdom integrations, but complete profile conformance and drop-in +Keycloak interchangeability are not established. + +## Implemented capabilities + +| Surface | What the current source provides | +| --- | --- | +| Issuer HTTP API | Discovery, `/authorize` and its callback/registration-return routes, `/token`, `/jwks`, `/userinfo`, local `/logout`, `/healthz` (liveness, probes nothing) and `/readyz` (probes LLDAP by bind, Authelia and privacyIDEA). | +| Human authentication | Authorization code with S256 PKCE, exact registered redirects, scope allow-lists, Authelia login, privacyIDEA MFA challenges, client-specific assurance requirements, browser session reuse, step-up/fresh-login handling, and configured registration/enrollment handoffs. The exchange is bound to the redirect URI the code was issued for, refused for clients not registered for the grant, authenticated for confidential clients, and the code is consumed atomically so exactly one exchange can succeed. Upstream Authelia ID tokens are verified — signature against the provider's published keys, expected issuer, own client ID in the audience, validity window — failing closed and independently of transport. | +| Service authentication | Static confidential `client_credentials` clients authenticated with form-encoded `client_secret_basic`; configured subject, tenant, roles, scopes and per-client token lifetime. Secrets are resolved from environment references at startup. | +| Tokens and identity | Locally signed RS256 JWTs; configurable access-token resource audience while ID tokens retain the client audience; human/service principal types, tenant, groups, roles, scope and assurance claims. A service tenant is bound at registration; a human tenant comes from the directory, or from a client-declared zone when the directory has placed the user nowhere, and issuance is refused when the two conflict. `assurance.at` is the authentication time, not the mint time. UserInfo verifies algorithm, issuer and access-token purpose, resolves canonical directory subjects, and filters profile/email/groups by scope. | +| Caller commands | `keycape login` for public-client browser PKCE login, `keycape service-token` for service exchange, and `keycape verify-client` to check a live registration's subject, tenant, roles, lifetime and scope refusals without emitting values. HTTPS discovery/JWKS verification and private JSON token-file delivery outside Git; no token output on stdout. | +| Validation and migration | Canonical snapshot checks; deterministic LLDAP user/group/membership export that records whether it enumerated the whole directory; basic Keycloak realm JSON; LDIF generation for OpenLDAP, 389 Directory Server and AD targets. These generate artifacts rather than execute a complete migration. | +| Diagnostics and packaging | Structured authentication/enforcement/migration events, liveness and dependency-probing readiness endpoints, Go build/test/vet targets, a container packaging the issuer and all four migration/validation binaries, and development/CI scaffolding. | + +## Material limits + +- Client registrations, service secrets and the signing key are loaded at startup. + Login, authorization and handoff state are process-local. There is no shared + session store, general refresh-token flow, or automatic signing-key/client-secret + rotation service. The default profile remains expiry-only. Proposed KEY-WP-0042 + adds an explicitly enabled, singleton, hash-only online access-token status + profile; see `docs/online-token-status.md` for its scope and admission limits. Logout clears the + local KeyCape session, not every upstream or downstream session/token. +- The optional tenant-engine `tenant_roles` adapter is wired through a + `tenantEngine` config block and is off unless `baseURL` is set (KEY-WP-0024). + When enabled it fails open: an unreachable source omits the claim rather than + failing issuance, so `tenant_roles` is a cache and must not be trusted for + privileged decisions. +- The LLDAP export enumerates the group subtree directly, so groups with no + members are present, and every snapshot carries a `groupEnumeration` field + saying whether that enumeration ran or the membership-derived fallback did + (KEY-WP-0018). A failed enumeration aborts the export rather than writing a + smaller snapshot, and read failures are reported rather than skipped. Users, + groups and memberships are sorted, so an unchanged directory exports + identically. Completeness beyond users, groups and memberships — passwords and + MFA credentials in particular — is still not covered. +- The Keycloak CLI takes a `-clients` config and migrates client registrations: + flows follow the declared grants, and audience, tenant, service subject and + roles are carried as protocol mappers with per-client lifetime, handoff URLs + and the secret *reference* as attributes (KEY-WP-0020). What it cannot carry it + names, in a report separate from the consistency check: secret values, MFA + policy enforcement, passwords and factor enrolment, and subject continuity — + Keycloak mints its own `sub`, so the LLDAP canonical ID survives only as an + attribute. The output is a reviewed artifact, not a proven migration; that + proof needs a live provider swap and is not established. +- Snapshot validation is a limited Go rule set, not full machine-readable schema + enforcement. Raw LDAP attribute keys are checked for descriptor validity, + canonical-mapping shadowing and case-only duplicates (KEY-WP-0021); attribute + values are not validated against a directory schema, and no allow-list of + permitted attribute names exists, since that field carries what the canonical + model does not name. The canonical YAML model and discovery metadata now match the + runtime client-registration surface and are held there by a conformance check + (KEY-WP-0017); the Go model is the runtime authority and the YAML the reviewed + contract. That is narrower than schema enforcement in general. +- The authorization-code grant now binds the redirect URI, + enforces grant-type eligibility, authenticates confidential clients and + consumes codes atomically, and UserInfo enforces algorithm, issuer and + access-token purpose (KEY-WP-0016). Upstream Authelia ID tokens are verified + before any claim is trusted — signature against the provider's published keys, + advertised issuer, own client ID in the audience, validity window — failing + closed and independently of transport (KEY-WP-0019). KeyCape deliberately does + not check or gate on the upstream transport. Enforcing these bindings + is not complete profile conformance. Relying parties must repeat `redirect_uri` + on the token exchange and present the access token, not the ID token, to + `/userinfo`; see [authorization-code bindings](docs/authorization-code-bindings.md). + See the assessment below. +- Tests cover local handlers, adapters, transformations and CLI protocol behavior. + They do not establish complete replacement against a running Keycloak/full-LDAP + stack. The Scenario B/C harnesses now run, and `src/tests/conformance` targets a + live issuer named by `KEYCAPE_TEST_ISSUER`, skipping when it is unset + (KEY-WP-0022). Run against Keycloak 26.0 it verified discovery, the + authorization surface, the published keys and a real token exchange — and + showed that stock Keycloak advertises the excluded `implicit` and `password` + grants, so it is not a drop-in for this profile. Directory migration is proved + end to end into a real OpenLDAP, with every migrated membership resolving, and a + realm built from a live export imports into Keycloak and serves discovery + (KEY-WP-0023). Relying-party behaviour and MFA against a migrated realm remain + unexercised, and credential/MFA migration is not supplied at all, so no harness + can establish it. Subject continuity is explicitly not preserved: the canonical + ID survives as an attribute while Keycloak mints its own `sub`. +- **All ten gaps from the 2026-09-05 assessment are now closed**, G10 last, on + 2026-09-10. Closure means the gap each named no longer holds; it does not mean + nothing is outstanding. The approver registration is published but no human has + logged in through it, the deployed image predates most of this revision, and the + Qonto rotation is deferred by owner decision on evidence. Those are open items + with owners and triggers, which is a different thing from a gap between what + this document claims and what is true. +- **Source behaviour and deployed behaviour are not the same claim, and this + document describes source.** Everything above is what the code in this revision + does, established by 394 Go tests across 23 packages plus the harness + runs named. What has been demonstrated against the live deployment is narrower and is + recorded as committed receipts in `docs/evidence/`: the upstream issuer was + probed and then pinned in `sso/keycape-config` + (`2026-09-09-upstream-issuer-pin.json` — the pre-pin state did **not** match, so + this was a real fix), and both approval service clients were verified live in an + attended owner process, showing JWKS signature verification, exact claims, + excess-scope and wrong-secret denial at a 900s lifetime + (`2026-09-09-keycape-verifier-admission.json`), independently confirmed by the + custody owner. That is live evidence for those two clients specifically — + more than a source claim, less than a general one. On the custody boundary: + client-side retrieval of those secrets is unadmitted and stays so, but the + attended operator path is not a client-side read and never required one; + conflating the two is what left this work recorded as waiting on a run that had + already happened. Those receipts also decline what they did not test: no real + predecessor rotation and no observed wall-clock expiry. Those name a **third + state** this document otherwise has no words for — *implemented and unproven*, + distinct from both missing and done. `verify-client`'s predecessor rejection is + written and unit-tested but has never run against a genuinely distinct + predecessor, so this repository holds a receipt that reads like rotation + evidence and is not. Both owners state that limit independently. Read "there is + a test" and "it has been exercised against the real thing" as separate claims + wherever this document makes one. The deployed image is older than this revision, so the + authorization-code bindings, upstream verification, readiness, graceful + shutdown and the two tenant-related changes are **not yet running anywhere** — + see [operations](docs/operations.md), "Unreleased fail-closed startup changes". + Read `docs/evidence/` before describing any of this as proven live. +- The server listens on HTTP; HTTPS termination is deployment-owned. + `/healthz` is liveness and probes nothing; `/readyz` probes LLDAP, Authelia and + privacyIDEA and gates traffic. `SIGTERM` drains in-flight requests for 15s. + The supported topology is a single replica, since login and authorization state + is process-local — see [operations](docs/operations.md) for that and for the + key-rotation, client-removal and logout limits. Development + Compose needs key and Authelia material absent from the checkout by design; + `scripts/bootstrap-dev.sh` generates it locally and git-ignores it + (KEY-WP-0026). Production + deployment and custody are external; a source implementation or example client + fragment does not prove live registration or consumer cutover. +- Notification receipt closure is done: KEY-WP-0009 is finished, its four handoffs + delivered with receipts readable via `GET /messages/?from_agent=key-cape`. + Approval-client provisioning (KEY-WP-0013-T02) and coordinated Qonto rotation + plus the login-proxy consumer split (KEY-WP-0014-T04) remain open. Both requests + are now delivered and both wait on owners outside this repository — + railiance-platform for custody admission, approval-engine for the exact human + callback, ops-warden for the route contract and a rotation execution/rollback + authority. No client is materialized in the deployed image and no route changed. + +## Deliberate exclusions + +General-purpose IAM, dynamic client registration, implicit/password grants, +wildcard redirects, arbitrary identity brokering, resource authorization policy, +OpenBao credential custody, and Keycloak operations are outside this repository's +ownership. Registration/enrollment handoffs do not implement user provisioning +or factor enrollment themselves. + +## Entry points and verification + +Run the issuer with `keycape --config PATH`; `keycape server` and +`keycape migrate` are not implemented subcommands. Caller commands are +`keycape login`, `keycape service-token` and `keycape verify-client`. +`keycape check-config` validates a rendered configuration offline, before a +restart (see [operations](docs/operations.md)). + +Separate binaries under `src/cmd/` are `validator`, `lldap-export`, +`keycape-to-keycloak`, and `lldap-to-ldap`. `make -C src build` places binaries in +root `bin/` — the root `make build` only compiles and installs nothing. The +container packages all five binaries with the issuer as its entrypoint +(KEY-WP-0026). Tests live under `src/tests/` and alongside packages in +`src/internal/`. + +`make test`, `make lint` (Go vet), `make build`, and `make contract-test` are the +root checks. The capability-contract check requires the sibling NetKingdom +validator. These checks passed for the reviewed revision in the preceding +implementation session and were rerun for this one; `src/tests/conformance` is +additionally runnable against a live issuer via `KEYCAPE_TEST_ISSUER` and skips +when it is unset. The scope assessment is a source review: it establishes what +the code does, not what any deployment is currently running. + +Use this repo for its bounded issuer, caller JWT acquisition, identity adapters +and migration preparation. Consult [INTENT.md](INTENT.md) for the architectural +target, [native authentication](docs/native-authentication.md) for caller +constraints, [operations](docs/operations.md) for the supported topology and the +unreleased fail-closed changes, `docs/evidence/` for what has been proven against +the live deployment, and the [reassessment](history/2026-09-09-scope-reassessment.md) +— with the [2026-09-05 assessment](history/2026-09-05-011726-scope-intent-assessment.md) +it supersedes — for evidence, priorities and remaining gaps. + +## Provided capabilities + +```capability +type: security +title: Bounded OIDC and service-token issuance +description: Implements static-client browser PKCE and client-credentials authentication with RS256 identity claims through Authelia, LLDAP and privacyIDEA; complete Keycloak interchangeability remains unproven. +keywords: [oidc, pkce, authentication, iam, sso, mfa, identity, service-token] +``` + +```capability +type: security +title: Verified caller JWT acquisition +description: Provides native browser login and service-token exchange commands with HTTPS discovery, JWKS verification and private token-file delivery. +keywords: [oidc, pkce, jwt, cli, authentication, jwks] +``` + +```capability +type: security +title: Directory validation and migration artifact generation +description: Checks canonical snapshots and produces LLDAP exports, basic Keycloak realm JSON and target-specific LDIF; does not perform or prove a complete live migration. +keywords: [migration, identity, lldap, keycloak, ldif, validation] +``` diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/online-token-status.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/online-token-status.md new file mode 100644 index 0000000..331e6df --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/online-token-status.md @@ -0,0 +1,119 @@ +# Optional online access-token status + +Source proposal under KEY-WP-0042, driven by CUST-WP-0080 and existing +NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09. Independent issuer/consumer +contract review and native admission remain required. Existing deployments are +unchanged. This extends the bounded issuer; it does not introduce a broker, +approval store, PDP, or a second application-local token registry. + +The legacy profile authenticates self-contained JWTs until expiry. That does +not establish that an operation's initiating token is still unrevoked. The +optional profile adds issuer-owned current access-token status using +[RFC 7662](https://www.rfc-editor.org/rfc/rfc7662.html) introspection and +[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009.html) revocation. It supports +access tokens only, without refresh tokens or token exchange. + +## Enablement and lifecycle + +Owner-admitted configuration opts in explicitly: + +```yaml +tokenStatus: + enabled: true + maxEntries: 10000 +``` + +The issuer must use HTTPS at its existing trusted ingress. The server's internal +HTTP listener and reverse-proxy topology are unchanged; direct HTTP exposure is +not admitted by an HTTPS issuer string. Omitted configuration keeps the existing +expiry-only token/UserInfo contract and advertises no new endpoints. Enabling +status registers `/introspect` and `/revoke`, advertises their implemented client +authentication methods, and connects the same store to issuance and UserInfo. + +Human PKCE and service grants add a cryptographically random `jti` to each access +token. ID-token claims and existing assurance/tenant provenance remain unchanged. +Distinct grants in the same second have distinct bearer tokens: a new grant can +never remint a revoked token. A successful response is emitted only after the +token hash, expiry and current registration commitment have been recorded. No +bearer, client secret or identity/group snapshot is retained in the status store. + +The store is bounded to the configured count (default 10000, maximum 1000000). +Live records and revocation tombstones are never evicted to admit another grant; +capacity exhaustion refuses issuance with a fixed 503. Expired entries are +collected when admitting a new token. Capacity and per-client ingress rate limits +need workload-representative admission: this is not a throughput/availability +claim or protection against an authorized client filling the bound. + +This profile uses KeyCape's existing **single-instance topology**. Status is +process-local. Restart, deployment, rollback or store loss makes every earlier +token inactive at online endpoints, even if its JWT signature/expiry still +validates offline. Consumers must reauthenticate; there is no recovery replay +or token-state import that can resurrect revoked authority. Multiple replicas +are unsupported. Native admission must include this interruption and the +measured reauthentication/recovery procedure. A persisted/shared store is not +introduced here. + +## Introspection and revocation + +Both endpoints accept POST form bodies only, bound to 64 KiB; token values are +bound to 32 KiB. Query parameters, duplicate form or authentication fields, +unsupported content types and malformed authentication refuse without echoing +input. Responses use `Cache-Control: no-store` and `Pragma: no-cache`. + +Introspection requires the existing registered confidential client's +`client_secret_basic` authentication. Only that client's own issued tokens can +be inspected. A separate resource audience is returned as `aud`, not confused +with the issuing `client_id`. Cross-client resource-server delegation needs a +separately reviewed authorization contract and is not implicitly granted by a +matching audience string. Public clients cannot enumerate token status. + +An active response means the exact access token was issued in this process, +has not expired or been revoked, still matches its current complete registration, +and passes current issuer/signature/purpose validation. For humans, directory +lookup must succeed and the current account must be enabled and not suspended. +Status is checked again after directory lookup. Service subjects remain bound +to their current registration. Active output contains only standard access-token +metadata; inactive output contains only `active: false`. Unknown tokens, ID +tokens, altered tokens, another client's tokens and expired/revoked tokens all +return that same inactive response to an authenticated introspector. + +Revocation authenticates confidential clients using the same method. A registered +public client may revoke its own token using `client_id` plus possession of that +bearer; public-client introspection remains unavailable. Revocation only affects +the exact token issued to that client. Unknown, other-client, expired and already +revoked tokens all produce the same empty 200. No token existence is disclosed. +Token-type hints do not bypass the actual access-token lookup. Removal or changes +to a registration invalidate online status; the commitment explicitly includes +the secret digest and internal token lifetime omitted from public Client JSON. + +When enabled, actual UserInfo consults the same store before lookup and again +after it. Revocation therefore affects that existing endpoint immediately on its +next check. Ordinary login/logout and upstream Authelia sessions are distinct: +neither browser logout nor deleting a local cookie is represented as global +JWT revocation. + +## Consumer contract and remaining acceptance + +Online status establishes current access-token status, not permission +for an application resource or privileged effect. Consumers still independently +validate the actual signed token, admitted issuer/key/audience/type, subject, +tenant, principal type, assurance and freshness. The authenticated introspection +response must refer to that exact token/client. Obtain a new current response +before each protected operation; unavailable, malformed, inactive or ambiguous +responses refuse. A cached positive is not a current-session check. + +Access Engine retains resource/action decisions, Approval Engine exact one-shot +approval, Warden admission and owner adapters execution. Bind their existing +descriptor, case, transaction and exact effect before dispatch. A separate new +operation/inverse token must be acquired and revoked on every terminal path; +renewed authentication cannot revive a consumed approval or replay a mutation. +This issuer capability alone implements none of those consumer bindings. + +Native completion still requires independent issuer/profile review, exact +image/config/trust admission, bounded retained-token custody, real human/service +issuance, private transport, current-token/revocation integration in each owner, +all exit-path cleanup, restart/expiry/unavailable/refusal checks, ongoing +HTTP/API/Git/SSH authorization and audit evidence. Offline consumers which never +consult the endpoint retain their existing JWT validity; no estate-wide immediate +revocation claim follows from source implementation. Disabling the feature is +not an acceptable fallback for a consumer admitted under the online profile. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/operations.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/operations.md new file mode 100644 index 0000000..c4d8381 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/docs/operations.md @@ -0,0 +1,294 @@ +# Operating KeyCape + +The supported deployment topology and the limits that come with it. These are +deliberate boundaries of the current implementation, not defects awaiting a fix: +where a limit is a consequence of a design choice, the choice is named. + +## Topology: exactly one replica + +**Run one instance per issuer.** Authorization codes, login sessions and +registration/enrollment handoffs are held in process memory. There is no shared +store, and no sticky-session configuration makes this safe: a browser that starts +a login on one replica and returns from Authelia to another finds no session and +must start again. Two replicas do not halve the failure rate, they roughly double +the login failure rate. + +This is a documented exclusion rather than a missing feature. Adding a shared +session store is a real option if the profile ever requires horizontal scale; it +is not required today. + +Consequences to plan for: + +- **A restart drops in-flight logins.** Anyone mid-login gets an error and must + start again. In the default profile issued tokens are unaffected — they are + self-contained JWTs and + stay valid until they expire. +- **Rolling deployments are single-instance rollovers**, so expect a brief window + where new logins fail. Draining (below) protects requests already in flight, not + logins waiting on a human at Authelia's password prompt. + +## Liveness and readiness + +| Endpoint | Answers | Use it for | +| --- | --- | --- | +| `/healthz` | Is the process up? Probes nothing. | Liveness. | +| `/readyz` | Are LLDAP, Authelia and privacyIDEA reachable? | Readiness / traffic gating. | + +Keep these distinct. Wiring liveness to `/readyz` means an orchestrator restarts +KeyCape whenever a dependency blinks, turning someone else's blip into an outage +of your own — and a restart also discards every in-flight login, making it worse +than the condition it reacted to. + +`/readyz` returns 200 with `status: ready`, or 503 with `status: not_ready` and +the failing check named. It reports *which* check failed, never *why*: the +endpoint is unauthenticated and upstream error text carries hostnames and +occasionally credentials-in-URLs. The reason is in the server log. + +Probes are reachability and credential checks, not functional tests. LLDAP is +probed with a bind, so a rotated or revoked service password is caught — that +leaves the port open and every lookup failing, which is exactly what readiness +should catch. Authelia and privacyIDEA are probed with a plain HTTP GET: any +response means something is listening and speaking HTTP. + +Results are cached for 2 seconds and each probe is bounded at 3 seconds. The +cache is not an optimisation: the endpoint is necessarily unauthenticated, and +without it anyone able to reach it could drive one upstream request per +dependency per call. + +## Shutdown + +On `SIGTERM` or `SIGINT` the server stops accepting connections and gives +in-flight requests up to 15 seconds to finish. The grace period is deliberately +under the 30-second read/write timeouts, so a stuck request cannot outlive the +window an orchestrator typically allows before `SIGKILL`. + +In-memory login and authorization state is not preserved across shutdown, by +design — see the topology section. + +## Key and registration lifecycle + +The signing key and all client registrations are read once at startup. Both are +changed by editing configuration and restarting; there is no reload signal and no +rotation service. + +**Check a changed configuration before the restart.** KeyCape exits at startup on +a configuration it refuses, and with one replica that stops sign-in for every +client. `keycape check-config --config PATH [--env-names A,B] [--policy-store]` +runs every startup check that needs no secret value, signing key or upstream, and +prints a JSON report with no values in it. The checks cover validation (including +redirect URIs with `?` or `*`, and duplicate `clientId`s), building the client +registry, the global `tokenLifetime`, and the clients the policy store requires +when `--policy-store` is set. With `--env-names`, it also checks that every +`env:` secretRef is among the variables the Deployment provides. It exits non-zero +when the server would refuse to start. It does not check that the key file is +readable or that a secret is non-empty, because it never sees either. + +**The key ID is the constant `key-1`.** Rotating the signing key while keeping +that identifier is a trap: a consumer caching JWKS by `kid` may keep the old key +and reject freshly issued tokens until its cache expires. Plan rotation as +"publish new keys, let consumers refetch, then issue with the new key", and treat +a same-`kid` swap as a breaking change for anyone caching aggressively. + +**Removing a client registration does not revoke tokens already issued to it.** +In the default expiry-only profile there is no introspection or revocation +endpoint, so an issued token stays valid +until it expires — 15 minutes by default, or the client's `tokenLifetime`. To cut +off a compromised client, remove the registration *and* wait out the lifetime, or +rotate the signing key if you cannot. + +**`/logout` clears the local KeyCape session only.** It does not end the Authelia +session and does not revoke any issued token. A user who logs out and back in may +not be prompted for credentials, because the upstream session is still valid. + +## Transport + +The server speaks plain HTTP. TLS termination belongs to the deployment. KeyCape +deliberately does not check or gate on the transport used to reach its upstream +providers either; upstream ID tokens are verified cryptographically instead, so +that assurance does not depend on the network being what we believe it is +(KEY-WP-0019). + +## Upstream issuer pinning (read before rolling out KEY-WP-0019) + +KeyCape verifies upstream ID tokens against the issuer the provider advertises, +discovered server-side from `authelia.tokenBaseURL`. Some providers — Authelia +among them — derive the advertised issuer from the **request Host**, so the value +KeyCape learns over an in-cluster service address is not the value minted into +tokens issued for the browser-facing host. + +Where that is true, pin it explicitly: + +```yaml +authelia: + tokenBaseURL: "http://authelia.sso.svc.cluster.local:9091" + issuer: "https://auth.coulomb.social" # exactly the iss claim in ID tokens + jwksUrl: "http://authelia.sso.svc.cluster.local:9091/jwks.json" +``` + +Verification fails closed, so a mismatch means **every human login fails** — and +it looks like a broken login rather than a configuration error. Check the +issuer before rolling out, not after. The failure is diagnosable: the +authentication failure event carries `error_type=id_token_issuer_mismatch`, as +distinct from `id_token_signature` or `provider_keys_unavailable`. + +Confirm the value with the Host the provider will actually see: + +```bash +curl -s -H "Host: auth.coulomb.social" \ + http://authelia.sso.svc.cluster.local:9091/.well-known/openid-configuration \ + | jq -r .issuer +``` + +## Before any live change + +The estate-wide rules are in +`the-custodian/docs/agent-environment-orientation.md` (revision 2026-09-21); +read it first. The parts that apply to KeyCape's objects in `sso` and `mfa`: + +- The founder's go-ahead comes first, recorded as + `ADMINISTER @ realm:kubernetes/railiance01`, `activation=APPROVED`. Run changes + as `ssh railiance01 '…'`. A harness block is a stop signal, not something to + route around. +- Check CPU headroom before a restart or rollout + (`kubectl describe node | grep -A4 "Allocated resources"`). A rolling update + starts the new pod before stopping the old one. +- Diff first: client-side `kubectl diff`, then `--dry-run=server`. +- **Never `kubectl apply` a Secret, and never read one's metadata.** `apply` + copies the whole Secret into the `last-applied-configuration` annotation, so + even a metadata read prints the signing key and client secrets. Write Secrets + with `kubectl replace`. To test for the annotation without printing it: + `kubectl get secret -n sso -o go-template='{{ if index .metadata.annotations "kubectl.kubernetes.io/last-applied-configuration" }}HAS-ANNOTATION{{ else }}clean{{ end }}'`. + +- **Never run any other go-template or jsonpath against a Secret.** When a + template fails, for example `len` on an absent field, kubectl prints the whole + raw object as debugging output, `.data` included. The presence check above is + the only tested template. This leaked `sso/keycape-config` into an agent + session on 2026-09-23 (see below). + +**Open finding, 2026-09-23:** `sso/keycape-config`, `sso/authelia-secrets`, +`sso/lldap-secrets` and `mfa/privacyidea-config` all carry that annotation. The +earlier rotation script wrote them with `apply`; it now uses `replace`. Anyone +who can `get` these Secrets could read the data anyway, so this is not a new +reader. What it breaks is the assumption that a metadata read is safe. The fix +is a live change and waits for the founder's go-ahead: +`kubectl annotate secret -n kubectl.kubernetes.io/last-applied-configuration-` +for each of the four. It prints no value and leaves `.data` untouched, so pods +need no restart. Re-check with the template above afterwards. + +**Superseded the same day by an exposure.** While checking these Secrets' owners +before the cleanup, an agent ran an untested template. It failed and printed +`keycape-config` in full into the session transcript: the signing key, the LLDAP +bind password and the Authelia client secret. The other three Secrets were +probably printed as well. All four must be treated as exposed and rotated; the +annotation cleanup folds into that rotation. + +**Rotation deferred (founder decision, 2026-09-24, builder mode).** Tracked as +the-custodian `CUST-WP-0073-T05` with event triggers: the first production +workload or customer data, any sign the transcript left the workstation, or a +planned key rotation. The same workplan removes the problem class by giving +agents a cluster identity that cannot read these objects at all. + +## Current image (2026-10-15) + +The live issuer is +`forgejo.coulomb.social/coulomb/key-cape@sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010`, +source `8c6c071`. Over the previous live source `1164f65` it adds the +duplicate-clientId startup check (`c528daa`), the lost-authenticator help link +(`205a1c0`), and the opt-in sign-in diagnosis page with `uid`/`assurance` on +`auth_success` (`af3b1b0`). Founder-run on 2026-10-15: `keycape check-config` +on the live config was valid (13 clients, no errors), `kubectl diff` showed only +the image, server dry-run passed, Recreate rollout succeeded, pod ready with 0 +restarts and discovery answering 200. The owner manifest is +`net-kingdom/sso-mfa/k8s/keycape/deployment.yaml`. Rollback is +`sha256:6f79a2af1c695d39480173fad013facd84d21e7732860366af519302a2c496b8`. + +## Fail-closed startup changes from source 11ce29a + +These remain in the current image. They first shipped in +`sha256:8dc35801d1d2b0905a43530c40b1fed0107fc179141fe5b2b614c52c01714fc9`, +Forgejo run 69, source `11ce29a`, confirmed running on 2026-09-25. They fail +closed. The 2026-09-24 Vergabe journey completed after one token-exchange +failure; see `docs/evidence/2026-09-24-fresh-login-and-account-switch.md`. + +**1. Browser clients reject service-identity fields (`74b35b6`, KEY-WP-0028).** +Config validation now rejects `serviceSubject` or `roles` on a client whose +grant is not `client_credentials`; those fields were silently ignored there, so a +registration could look effective and do nothing. This is a **startup** error: +a stray field in the deployed config stops the process from booting rather than +degrading. The live `sso/keycape-config` was checked on 2026-09-09 and none of +its three browser clients carries either field, so it passes as deployed. The +custody owner confirmed independently that the config was last written during the +2026-09-09 activation (resource version 58747126) and has not been edited since, +so the inspected state is the state that will boot — and that they will re-check +the browser clients if anything writes that Secret before the window. `tenant` is deliberately not rejected; +see below. + +**2. A conflicting tenant binding refuses issuance (`329e48f`, KEY-WP-0013-T05).** +A client registration may declare a `tenant`, which is supplied when the +directory has placed the user nowhere and enforced when it has. A declared zone +that **conflicts** with a directory assignment refuses the token with `403` and +`error_type: tenant_binding`. No deployed client declares a tenant on a browser +grant today, so nothing changes on rollout; the refusal only becomes reachable +once the approver client is registered. See +[the tenant contract](tenant-claim-contract.md). + +**3. KeyCape enforces login freshness itself (`11ce29a`, KEY-WP-0033).** +`prompt=login` is no longer forwarded to Authelia, because 4.38 refuses it for +every real login. `prompt=login` and `max_age=0` go upstream as `max_age=10`, +and the callback refuses any upstream `auth_time` that misses the requirement, +or is absent (`stale_upstream_authentication`). This changes nothing for clients +that do not ask for freshness. The proof is a completed fresh-login journey, +not a redirect check. + +The live boot is the image above. A further rollout is not required for these +three changes. + +## What is not claimed + +No resource-efficiency or throughput bounds are asserted here. Nothing in this +repository benchmarks KeyCape, so any figure would be invention. Measure it in +your own deployment before sizing against it. + +## Account recovery and browser sign-out + +Set `KEYCAPE_ACCOUNT_PORTAL_URL=https://users.coulomb.social` and +`KEYCAPE_BROWSER_LOGOUT_URL=https://auth.coulomb.social/logout` together to enable +central browser recovery. Both must be HTTPS, with no credentials, query or +fragment. They override the corresponding YAML accountPortalURL/browserLogoutURL. +Failed browser authorization and expired callbacks redirect to the fixed +`/access-recovery` page with no state, code, claimed user or browser return URL. +API authorization/token validation remains fail-closed. + +`GET /account/logout` displays confirmation and creates a short-lived Secure, +HttpOnly host-only CSRF cookie. POST checks that cookie, the form nonce and exact +issuer Origin; only then does it invalidate the current KeyCape session and +redirect to Authelia's browser `/logout`, with a fixed portal `/logged-out` return. +Authelia owns and deletes its session cookie. Its v4.38 SignOut view validates the +return destination and performs the same-origin logout API call. Reference: +https://github.com/authelia/authelia/blob/v4.38.19/web/src/views/LoginPortal/SignOut/SignOut.tsx +Existing RP sessions and issued JWTs are not revoked. The original `/logout` +endpoint retains its local-only contract. Actual shared-session destruction +requires browser execution; a redirect-only smoke is not acceptance evidence. + +## Sign-in diagnosis page (NK-WP-0051-T02) + +A client with `signInDiagnosis: true` in its registration shows a page before +the redirect back to it: `uid`, `groups`, `assurance` and `client_id` as defined +in `net-kingdom/docs/sign-in-diagnosis.md`, with one continue link carrying the +unchanged code. Default is off. The repo enables it only for `openbao-admin` in +`config/dev-config.yaml`; the live flag is set on the OpenBao browser client +(`openbao-admin`) in `config.yaml` of the `sso/keycape-config` Secret, and takes +effect on the next issuer restart. It is live since 2026-10-15 (rv 75372162), +set with net-kingdom `sso-mfa/k8s/keycape/openbao-signin-diagnosis-rollout.py`; +`--operation remove` plus a restart is the rollback. A redirect URI that is not http(s) +skips the page. Every `auth_success` log line now carries `client_id`, `uid` and +`assurance`, and no token, code, email or one-time password. + +## Proposed online token-status profile + +KEY-WP-0042 adds an explicitly enabled singleton profile. It makes prior tokens +inactive at online endpoints after restart and keeps revoked-token tombstones +until expiry. It does not revoke offline consumers or upstream/browser sessions. +Read [the complete lifecycle and admission contract](online-token-status.md) +before proposing activation; no image/config rollout or consumer admission is +established by source tests. Existing deployed configuration remains unchanged. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/cmd/keycape/main.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/cmd/keycape/main.go new file mode 100644 index 0000000..a30559b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/cmd/keycape/main.go @@ -0,0 +1,488 @@ +// keycape is the main server binary for the KeyCape IAM profile service. +// It orchestrates Authelia, LLDAP, and privacyIDEA to implement the +// NetKingdom IAM Profile (OIDC/PKCE Authorization Code Flow). +package main + +import ( + "context" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "flag" + "fmt" + "keycape/internal/server/readiness" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/rs/zerolog" + + "keycape/internal/adapters/authelia" + "keycape/internal/adapters/lldap" + "keycape/internal/adapters/privacyidea" + "keycape/internal/adapters/tenantengine" + "keycape/internal/authclient" + "keycape/internal/config" + "keycape/internal/domain" + servererrors "keycape/internal/server/errors" + "keycape/internal/server/oidc" + "keycape/internal/server/policy" + "keycape/internal/server/telemetry" +) + +const version = "0.1.0" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "check-config" { + os.Exit(runCheckConfig(os.Args[2:], os.Stdout)) + } + + if len(os.Args) > 1 && os.Args[1] == "probe-upstream-issuer" { + if err := authelia.RunIssuerProbe(context.Background(), os.Args[2:], os.Stdout); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + if len(os.Args) > 1 && (os.Args[1] == "login" || os.Args[1] == "service-token" || os.Args[1] == "verify-client") { + if err := authclient.Run(context.Background(), os.Args[1:], os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + log := zerolog.New(os.Stdout).With().Timestamp().Logger() + + // ----------------------------------------------------------------- + // 1. Parse flags and load config. + // ----------------------------------------------------------------- + var cfgPath string + flag.StringVar(&cfgPath, "config", "", "path to YAML config file (env: KEYCAPE_CONFIG)") + flag.Parse() + + cfg, err := config.Load(cfgPath) + if err != nil { + log.Error().Err(err).Msg("failed to load config") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 2. Validate config. + // ----------------------------------------------------------------- + errs := config.ValidateConfig(cfg) + if len(errs) > 0 { + log.Error().Strs("errors", errs).Msg("config validation failed") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 3. Load RSA private key. + // ----------------------------------------------------------------- + privateKey, err := loadPrivateKey(cfg.PrivateKeyPEM) + if err != nil { + log.Error().Err(err).Str("path", cfg.PrivateKeyPEM).Msg("failed to load private key") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 4. Build JWKS from public key. + // ----------------------------------------------------------------- + ks := oidc.NewKeySet() + ks.AddKey("key-1", &privateKey.PublicKey) + + // ----------------------------------------------------------------- + // 5. Build client registry. + // ----------------------------------------------------------------- + clients, err := buildClientRegistry(cfg.Clients) + if err != nil { + log.Error().Err(err).Msg("failed to build client registry") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 6. Create adapters. + // ----------------------------------------------------------------- + lldapAdapter := lldap.New(cfg.LLDAP) + autheliaAdapter := authelia.New(cfg.Authelia, nil) + privacyIDEAAdapter := privacyidea.New(cfg.PrivacyIDEA, nil) + + // ----------------------------------------------------------------- + // 7. Create telemetry emitter. + // ----------------------------------------------------------------- + emitter := telemetry.NewLogEmitter(log) + + // ----------------------------------------------------------------- + // 8. Create enforcement registry. + // ----------------------------------------------------------------- + enforcement := servererrors.DefaultRegistry() + + // ----------------------------------------------------------------- + // 9. Create session store. + // ----------------------------------------------------------------- + sessions := oidc.NewSessionStore() + + // ----------------------------------------------------------------- + // 10. Parse token lifetime. + // ----------------------------------------------------------------- + tokenLifetime := 15 * time.Minute + if cfg.TokenLifetime != "" { + d, err := time.ParseDuration(cfg.TokenLifetime) + if err != nil { + log.Error().Err(err).Str("tokenLifetime", cfg.TokenLifetime).Msg("invalid tokenLifetime") + os.Exit(1) + } + tokenLifetime = d + } + + // ----------------------------------------------------------------- + // 11. Build issuer base URL. + // ----------------------------------------------------------------- + issuer := strings.TrimRight(cfg.Issuer, "/") + + // ----------------------------------------------------------------- + // 12. Register HTTP handlers. + // ----------------------------------------------------------------- + mux := http.NewServeMux() + var tokenStatus *oidc.TokenStatusStore + var introspectionEndpoint, revocationEndpoint string + if cfg.TokenStatus.Enabled { + limit := cfg.TokenStatus.MaxEntries + if limit == 0 { + limit = 10000 + } + var err error + tokenStatus, err = oidc.NewTokenStatusStore(limit) + if err != nil { + log.Error().Msg("invalid token-status configuration") + os.Exit(1) + } + status := &oidc.TokenStatusHandler{Tokens: tokenStatus, ClientConfig: clients, Users: lldapAdapter, SigningKey: &privateKey.PublicKey, Issuer: issuer} + mux.HandleFunc("/introspect", status.Introspect) + mux.HandleFunc("/revoke", status.Revoke) + introspectionEndpoint, revocationEndpoint = issuer+"/introspect", issuer+"/revoke" + } + + // Discovery. + mux.Handle("/.well-known/openid-configuration", oidc.NewDiscoveryHandler(oidc.DiscoveryConfig{ + Issuer: issuer, + AuthorizationEndpoint: issuer + "/authorize", + TokenEndpoint: issuer + "/token", + JWKSUri: issuer + "/jwks", + UserinfoEndpoint: issuer + "/userinfo", + EndSessionEndpoint: issuer + "/logout", + IntrospectionEndpoint: introspectionEndpoint, + RevocationEndpoint: revocationEndpoint, + Clients: clients, + })) + + // JWKS. + mux.Handle("/jwks", oidc.NewJWKSHandler(ks)) + + // Authorize handler (with enforcement middleware). + logins := oidc.NewLoginSessionStore() + authorizeHandler := &oidc.AuthorizeHandler{ + AccountPortalURL: cfg.AccountPortalURL, + ClientConfig: clients, + Auth: autheliaAdapter, + MFA: privacyIDEAAdapter, + Sessions: sessions, + Logins: logins, + Handoffs: oidc.NewHandoffStore(), + Issuer: issuer, + Emitter: emitter, + Users: lldapAdapter, + } + if path := os.Getenv("KEYCAPE_POLICY_PATH"); path != "" { + policies, err := policy.Open(path, clients) + if err != nil { + log.Error().Msg("authentication policy store unavailable") + os.Exit(1) + } + authorizeHandler.EffectivePolicy = policies.Effective + mux.Handle("/platform/authentication-policy", policy.Handler(policies, issuer, &privateKey.PublicKey)) + } + mux.Handle("/authorize", enforcement.Middleware(authorizeHandler)) + mux.Handle("/authorize/callback", authorizeHandler) + mux.Handle("/authorize/return", authorizeHandler) + mux.Handle("/authorize/register", authorizeHandler) + mux.Handle("/logout", &oidc.LogoutHandler{ + ClientConfig: clients, + Logins: logins, + SecureCookie: strings.HasPrefix(strings.ToLower(issuer), "https://"), + }) + + if cfg.AccountPortalURL != "" { + mux.Handle("/account/logout", &oidc.AccountLogoutHandler{ + PortalURL: cfg.AccountPortalURL, UpstreamLogoutURL: cfg.BrowserLogoutURL, + Logins: logins, Issuer: issuer, + }) + } + + // Token handler (with enforcement middleware). + tokenHandler := &oidc.TokenHandler{ + ClientConfig: clients, + Sessions: sessions, + Users: lldapAdapter, + SigningKey: privateKey, + Issuer: issuer, + TokenLifetime: tokenLifetime, + Emitter: emitter, + // Opt-in: nil unless tenantEngine.baseURL is configured, which is what + // leaves tenant_roles off the stock server (KEY-WP-0024). The adapter + // fails open, so a configured-but-unreachable cache omits the claim + // rather than failing issuance. + TenantEngine: buildTenantEngine(cfg.TenantEngine), + Tokens: tokenStatus, + } + mux.Handle("/token", enforcement.Middleware(tokenHandler)) + + // Userinfo handler. + mux.Handle("/userinfo", &oidc.UserinfoHandler{ + Users: lldapAdapter, + SigningKey: &privateKey.PublicKey, + Issuer: issuer, + Emitter: emitter, + Tokens: tokenStatus, + ClientConfig: clients, + }) + + // Healthz. + // /readyz gates traffic on the dependencies a login needs; /healthz stays a + // liveness signal (KEY-WP-0025). Restarting KeyCape because LLDAP blinked + // would convert a dependency blip into an outage of our own, so the two must + // not be the same endpoint. + mux.Handle("/readyz", readiness.New(2*time.Second, 3*time.Second, + readiness.Check{Name: "lldap", Probe: func(ctx context.Context) error { + return lldapAdapter.Ping(ctx) + }}, + readiness.Check{Name: "authelia", Probe: httpReachable(cfg.Authelia.TokenBaseURL, cfg.Authelia.BaseURL)}, + readiness.Check{Name: "privacyidea", Probe: httpReachable(cfg.PrivacyIDEA.BaseURL)}, + readiness.Check{Name: "factor_reader", Probe: privacyIDEAAdapter.Probe}, + )) + + // Liveness only: this says the process is up, and deliberately probes + // nothing. See /readyz. + mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(map[string]string{ + "status": "ok", + "version": version, + }) + }) + + // Inject emitter into request context. + handler := withEmitter(mux, emitter) + + // ----------------------------------------------------------------- + // 13. Start HTTP server. + // ----------------------------------------------------------------- + addr := fmt.Sprintf(":%d", cfg.Port) + if cfg.Port == 0 { + addr = ":8080" + } + + log.Info(). + Str("issuer", issuer). + Str("addr", addr). + Str("environment", cfg.Environment). + Str("version", version). + Msg("starting keycape server") + + srv := &http.Server{ + Addr: addr, + Handler: handler, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + + // Serve in the background so shutdown can be driven by a signal. Without + // this the process died on SIGTERM mid-request, so every rolling restart + // returned errors to whoever was logging in at that moment. + serveErr := make(chan error, 1) + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + serveErr <- err + return + } + serveErr <- nil + }() + + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + + select { + case err := <-serveErr: + if err != nil { + log.Error().Err(err).Msg("server error") + os.Exit(1) + } + case sig := <-signals: + log.Info().Str("signal", sig.String()).Msg("shutting down") + // In-flight requests get a bounded window to finish. In-memory login and + // authorization state does not survive this and is not meant to: a + // browser mid-login must start again after a restart. That is a + // documented limit of the single-replica topology, not a bug to paper + // over here -- see docs/operations.md. + ctx, cancel := context.WithTimeout(context.Background(), shutdownGrace) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("graceful shutdown failed; exiting anyway") + os.Exit(1) + } + log.Info().Msg("shutdown complete") + } +} + +// shutdownGrace bounds how long in-flight requests may finish after a signal. +// Kept under the 30s read/write timeouts so a stuck request cannot outlive the +// window an orchestrator typically allows before sending SIGKILL. +const shutdownGrace = 15 * time.Second + +// httpReachable probes the first non-empty URL with a GET, reporting whether the +// dependency answered at all. It is a reachability check, not a functional one: +// any HTTP response means something is listening and speaking HTTP there. +func httpReachable(candidates ...string) func(context.Context) error { + target := "" + for _, candidate := range candidates { + if candidate != "" { + target = candidate + break + } + } + return func(ctx context.Context) error { + if target == "" { + return fmt.Errorf("no endpoint configured") + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return nil + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +// loadPrivateKey reads a PEM file and parses the RSA private key. +// Supports both PKCS#1 ("RSA PRIVATE KEY") and PKCS#8 ("PRIVATE KEY") PEM blocks. +func loadPrivateKey(path string) (*rsa.PrivateKey, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read key file: %w", err) + } + + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("no PEM block found in %q", path) + } + + switch block.Type { + case "RSA PRIVATE KEY": + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse PKCS1 private key: %w", err) + } + return key, nil + case "PRIVATE KEY": + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse PKCS8 private key: %w", err) + } + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("private key is not an RSA key") + } + return rsaKey, nil + default: + return nil, fmt.Errorf("unexpected PEM block type %q; expected RSA PRIVATE KEY or PRIVATE KEY", block.Type) + } +} + +// buildClientRegistry converts []ClientConfig into the map used by handlers. +func buildClientRegistry(cfgClients []config.ClientConfig) (map[string]*domain.Client, error) { + return buildClientRegistryWith(cfgClients, os.Getenv) +} + +// buildClientRegistryWith resolves env: secretRefs through getenv, so +// check-config can build the registry without any secret value. +func buildClientRegistryWith(cfgClients []config.ClientConfig, getenv func(string) string) (map[string]*domain.Client, error) { + m := make(map[string]*domain.Client, len(cfgClients)) + for i := range cfgClients { + c := &cfgClients[i] + clientSecret := "" + var clientTokenLifetime time.Duration + if strings.HasPrefix(c.SecretRef, "env:") { + envName := strings.TrimPrefix(c.SecretRef, "env:") + clientSecret = getenv(envName) + if clientSecret == "" { + return nil, fmt.Errorf("client %q secret environment variable %q is empty", c.ClientID, envName) + } + } + if c.TokenLifetime != "" { + parsedLifetime, parseErr := time.ParseDuration(c.TokenLifetime) + if parseErr != nil { + return nil, fmt.Errorf("client %q tokenLifetime is invalid: %w", c.ClientID, parseErr) + } + clientTokenLifetime = parsedLifetime + } + m[c.ClientID] = &domain.Client{ + ClientID: c.ClientID, + DisplayName: c.DisplayName, + RedirectURIs: c.RedirectURIs, + AllowedScopes: c.AllowedScopes, + GrantTypes: c.GrantTypes, + ClientType: c.ClientType, + SecretRef: c.SecretRef, + ClientSecret: clientSecret, + Audience: c.Audience, + ServiceSubject: c.ServiceSubject, + Tenant: c.Tenant, + Roles: c.Roles, + TokenLifetime: clientTokenLifetime, + MFARequired: c.MFARequired, + MFAOptional: c.MFAOptional, + RegistrationURL: c.RegistrationURL, + EnrollmentURL: c.EnrollmentURL, + SignInDiagnosis: c.SignInDiagnosis, + } + } + return m, nil +} + +// withEmitter wraps a handler to inject the telemetry emitter into every request context. +func withEmitter(next http.Handler, e telemetry.Emitter) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := telemetry.WithEmitter(context.Background(), e) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// buildTenantEngine returns the tenant_roles cache client, or nil when the +// claim is not configured. Validation has already accepted the URL and timeout. +func buildTenantEngine(cfg config.TenantEngineConfig) *tenantengine.Client { + if cfg.BaseURL == "" { + return nil + } + var httpClient *http.Client + if cfg.Timeout != "" { + if timeout, err := time.ParseDuration(cfg.Timeout); err == nil { + httpClient = &http.Client{Timeout: timeout} + } + } + return tenantengine.New(cfg.BaseURL, httpClient) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/config.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/config.go new file mode 100644 index 0000000..6376708 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/config.go @@ -0,0 +1,152 @@ +// Package config handles loading and validating the KeyCape server configuration +// from a YAML file. The config path is resolved from the --config flag or the +// KEYCAPE_CONFIG environment variable. +package config + +import ( + "fmt" + "os" + "time" + + "gopkg.in/yaml.v3" + + "keycape/internal/adapters/authelia" + "keycape/internal/adapters/lldap" + "keycape/internal/adapters/privacyidea" + "keycape/internal/domain" +) + +// Config is the top-level server configuration. +type Config struct { + AccountPortalURL string `yaml:"accountPortalURL,omitempty"` + BrowserLogoutURL string `yaml:"browserLogoutURL,omitempty"` + Issuer string `yaml:"issuer"` + Port int `yaml:"port"` + TokenLifetime string `yaml:"tokenLifetime"` + PrivateKeyPEM string `yaml:"privateKeyPem"` + LLDAP lldap.Config `yaml:"lldap"` + Authelia authelia.Config `yaml:"authelia"` + PrivacyIDEA privacyidea.Config `yaml:"privacyidea"` + Clients []ClientConfig `yaml:"clients"` + Environment string `yaml:"environment"` + TenantEngine TenantEngineConfig `yaml:"tenantEngine,omitempty"` + TokenStatus TokenStatusConfig `yaml:"tokenStatus,omitempty"` +} + +// TokenStatusConfig opts a singleton issuer into online access-token status. +// Existing deployment configuration remains expiry-only until owner admission. +type TokenStatusConfig struct { + Enabled bool `yaml:"enabled"` + MaxEntries int `yaml:"maxEntries,omitempty"` +} + +// TenantEngineConfig configures the optional tenant_roles cache claim. +// +// Opt-in by design: an empty baseURL leaves the claim off entirely, which is +// what the stock server does. tenant_roles is a cache callers must not trust for +// privileged decisions, and the adapter fails open, so enabling it is a +// performance choice rather than a security one (KEY-WP-0024). +type TenantEngineConfig struct { + // BaseURL is tenant-engine's cache-read endpoint. Empty disables the claim. + BaseURL string `yaml:"baseURL,omitempty"` + + // Timeout bounds the lookup, which sits on the synchronous token-issuance + // path. Empty uses the adapter's own short default. + Timeout string `yaml:"timeout,omitempty"` +} + +// ClientConfig is a static OIDC client registration. +type ClientConfig struct { + ClientID string `yaml:"clientId"` + Audience string `yaml:"audience,omitempty"` + DisplayName string `yaml:"displayName"` + RedirectURIs []string `yaml:"redirectUris"` + AllowedScopes []string `yaml:"allowedScopes"` + GrantTypes []string `yaml:"grantTypes"` + ClientType string `yaml:"clientType"` // "confidential" | "public" + SecretRef string `yaml:"secretRef,omitempty"` + ServiceSubject string `yaml:"serviceSubject,omitempty"` + Tenant string `yaml:"tenant,omitempty"` + Roles []string `yaml:"roles,omitempty"` + TokenLifetime string `yaml:"tokenLifetime,omitempty"` + MFAOptional bool `yaml:"mfaOptional,omitempty"` + MFARequired *bool `yaml:"mfaRequired,omitempty"` + RegistrationURL string `yaml:"registrationUrl,omitempty"` + EnrollmentURL string `yaml:"enrollmentUrl,omitempty"` + // SignInDiagnosis opts the client into the pre-redirect sign-in + // diagnosis page (NK-WP-0051-T02). Default off. + SignInDiagnosis bool `yaml:"signInDiagnosis,omitempty"` +} + +// Load reads and parses the YAML config file at path. +// If path is empty, it falls back to the KEYCAPE_CONFIG environment variable. +// Returns an error if the file cannot be read or parsed. +func Load(path string) (*Config, error) { + if path == "" { + path = os.Getenv("KEYCAPE_CONFIG") + } + if path == "" { + return nil, fmt.Errorf("config: no config path specified (use --config or KEYCAPE_CONFIG)") + } + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("config: read %q: %w", path, err) + } + + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, fmt.Errorf("config: parse %q: %w", path, err) + } + + if value := os.Getenv("KEYCAPE_ACCOUNT_PORTAL_URL"); value != "" { + cfg.AccountPortalURL = value + } + if value := os.Getenv("KEYCAPE_BROWSER_LOGOUT_URL"); value != "" { + cfg.BrowserLogoutURL = value + } + return &cfg, nil +} + +// Registrations converts the configured clients to domain registrations without +// resolving any secret. Migration and inspection tooling needs the registration +// contract — grants, audience, tenant, roles, lifetime, MFA and handoff policy — +// but must never load secret material, so ClientSecret is deliberately left +// empty here. The server has its own conversion that does resolve secrets. +// +// An unparseable tokenLifetime is an error rather than a silent zero: a +// migration that quietly drops a per-client lifetime is the class of defect this +// exists to avoid. +func (c *Config) Registrations() ([]domain.Client, error) { + clients := make([]domain.Client, 0, len(c.Clients)) + for _, cc := range c.Clients { + var lifetime time.Duration + if cc.TokenLifetime != "" { + parsed, err := time.ParseDuration(cc.TokenLifetime) + if err != nil { + return nil, fmt.Errorf("config: client %q tokenLifetime is invalid: %w", cc.ClientID, err) + } + lifetime = parsed + } + clients = append(clients, domain.Client{ + ClientID: cc.ClientID, + DisplayName: cc.DisplayName, + RedirectURIs: cc.RedirectURIs, + AllowedScopes: cc.AllowedScopes, + GrantTypes: cc.GrantTypes, + ClientType: cc.ClientType, + SecretRef: cc.SecretRef, + Audience: cc.Audience, + ServiceSubject: cc.ServiceSubject, + Tenant: cc.Tenant, + Roles: cc.Roles, + TokenLifetime: lifetime, + MFARequired: cc.MFARequired, + MFAOptional: cc.MFAOptional, + RegistrationURL: cc.RegistrationURL, + EnrollmentURL: cc.EnrollmentURL, + SignInDiagnosis: cc.SignInDiagnosis, + }) + } + return clients, nil +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/token_status_test.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/token_status_test.go new file mode 100644 index 0000000..0d1278d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/token_status_test.go @@ -0,0 +1,41 @@ +package config_test + +import ( + "strings" + "testing" + + "keycape/internal/config" +) + +func TestTokenStatusConfigIsExplicitBoundedAndHTTPS(t *testing.T) { + for _, kind := range []string{"legacy", "enabled-default", "enabled-bounded", "http", "userinfo-url", "query", "too-large", "negative", "disabled-capacity"} { + t.Run(kind, func(t *testing.T) { + cfg := validConfig("unused-public-test-key-path") + cfg.TokenStatus.Enabled = kind != "legacy" && kind != "disabled-capacity" + switch kind { + case "enabled-bounded": + cfg.TokenStatus.MaxEntries = 128 + case "http": + cfg.Issuer = "http://auth.example.com" + case "userinfo-url": + cfg.Issuer = "https://name@auth.example.com" + case "query": + cfg.Issuer = "https://auth.example.com?query=present" + case "too-large": + cfg.TokenStatus.MaxEntries = 1000001 + case "negative": + cfg.TokenStatus.MaxEntries = -1 + case "disabled-capacity": + cfg.TokenStatus.MaxEntries = 128 + } + var statusError bool + for _, err := range config.ValidateConfig(cfg) { + statusError = statusError || strings.HasPrefix(err, "tokenStatus") + } + expected := kind != "legacy" && kind != "enabled-default" && kind != "enabled-bounded" + if statusError != expected { + t.Fatal("token-status configuration acceptance mismatch") + } + }) + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/validate.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/validate.go new file mode 100644 index 0000000..8df8151 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/config/validate.go @@ -0,0 +1,199 @@ +package config + +import ( + "fmt" + "net/url" + "strings" + "time" +) + +// ValidateConfig validates a loaded Config and returns a list of human-readable +// error messages. An empty slice means the config is valid. +// Called at startup — the server must exit 1 if any errors are returned. +func ValidateConfig(cfg *Config) []string { + var errs []string + if cfg.TokenStatus.Enabled { + u, err := url.Parse(cfg.Issuer) + if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" { + errs = append(errs, "tokenStatus: requires an HTTPS issuer without credentials, query or fragment") + } + if cfg.TokenStatus.MaxEntries < 0 || cfg.TokenStatus.MaxEntries > 1000000 { + errs = append(errs, "tokenStatus.maxEntries: must be between 1 and 1000000, or omitted for 10000") + } + } else if cfg.TokenStatus.MaxEntries != 0 { + errs = append(errs, "tokenStatus.maxEntries: set while token status is disabled") + } + for name, raw := range map[string]string{"accountPortalURL": cfg.AccountPortalURL, "browserLogoutURL": cfg.BrowserLogoutURL} { + if raw == "" { + continue + } + u, err := url.Parse(raw) + if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" { + errs = append(errs, name+": must be an HTTPS URL without credentials, query or fragment") + } + } + if (cfg.AccountPortalURL == "") != (cfg.BrowserLogoutURL == "") { + errs = append(errs, "accountPortalURL and browserLogoutURL must be configured together") + } + + // Issuer must be a valid URL with an http(s) scheme. + if cfg.Issuer == "" { + errs = append(errs, "issuer: must not be empty") + } else { + u, err := url.Parse(cfg.Issuer) + if err != nil || u.Scheme == "" || u.Host == "" { + errs = append(errs, fmt.Sprintf("issuer: %q is not a valid URL (must include scheme and host)", cfg.Issuer)) + } else if u.Scheme != "http" && u.Scheme != "https" { + errs = append(errs, fmt.Sprintf("issuer: scheme must be http or https, got %q", u.Scheme)) + } + } + + // Port must be in the valid TCP range. + if cfg.Port < 1 || cfg.Port > 65535 { + errs = append(errs, fmt.Sprintf("port: must be between 1 and 65535, got %d", cfg.Port)) + } + + // tenant_roles is opt-in: an empty baseURL disables it. A configured one + // must be usable, since a misconfigured cache source on the token path is + // worse than no cache source (KEY-WP-0024). + if cfg.TenantEngine.BaseURL != "" { + u, err := url.Parse(cfg.TenantEngine.BaseURL) + if err != nil || u.Scheme == "" || u.Host == "" { + errs = append(errs, fmt.Sprintf("tenantEngine.baseURL: %q is not a valid URL (must include scheme and host)", cfg.TenantEngine.BaseURL)) + } else if u.Scheme != "http" && u.Scheme != "https" { + errs = append(errs, fmt.Sprintf("tenantEngine.baseURL: scheme must be http or https, got %q", u.Scheme)) + } + } + if cfg.TenantEngine.Timeout != "" { + if cfg.TenantEngine.BaseURL == "" { + errs = append(errs, "tenantEngine.timeout: set without a baseURL, so tenant_roles stays disabled") + } else if timeout, err := time.ParseDuration(cfg.TenantEngine.Timeout); err != nil { + errs = append(errs, "tenantEngine.timeout: must be a valid duration") + } else if timeout <= 0 || timeout > 10*time.Second { + errs = append(errs, "tenantEngine.timeout: must be greater than 0 and at most 10s; it sits on the token-issuance path") + } + } + + // At least one client must be registered. + if len(cfg.Clients) == 0 { + errs = append(errs, "clients: at least one client must be defined") + } + + // The registry is keyed by clientId, so a duplicate would silently replace + // the earlier registration instead of failing. + seen := map[string]bool{} + + // Each client must have at least one redirect URI and a non-empty clientId. + for i, c := range cfg.Clients { + prefix := fmt.Sprintf("clients[%d] (%s)", i, c.ClientID) + if c.ClientID == "" { + prefix = fmt.Sprintf("clients[%d]", i) + errs = append(errs, prefix+": clientId must not be empty") + } else if seen[c.ClientID] { + errs = append(errs, prefix+": clientId is registered more than once") + } + seen[c.ClientID] = true + if strings.TrimSpace(c.Audience) != c.Audience || strings.ContainsAny(c.Audience, " \t\r\n") { + errs = append(errs, prefix+": audience must be a single non-whitespace identifier") + } + if c.MFAOptional && c.MFARequired != nil { + errs = append(errs, prefix+": mfaOptional cannot be combined with mfaRequired") + } + if c.MFAOptional && contains(c.GrantTypes, "client_credentials") { + errs = append(errs, prefix+": mfaOptional is only supported for browser clients") + } + hasAuthorizationCode := contains(c.GrantTypes, "authorization_code") + hasClientCredentials := contains(c.GrantTypes, "client_credentials") + if (hasAuthorizationCode || !hasClientCredentials) && len(c.RedirectURIs) == 0 { + errs = append(errs, prefix+": redirect_uri: at least one redirectUri must be registered") + } + if hasClientCredentials { + if c.ClientType != "confidential" { + errs = append(errs, prefix+": client_credentials requires clientType confidential") + } + if !strings.HasPrefix(c.SecretRef, "env:") { + errs = append(errs, prefix+": client_credentials requires an env: secretRef") + } + if c.ServiceSubject == "" || c.Tenant == "" { + errs = append(errs, prefix+": client_credentials requires serviceSubject and tenant") + } + if c.TokenLifetime != "" { + lifetime, err := time.ParseDuration(c.TokenLifetime) + if err != nil { + errs = append(errs, prefix+": tokenLifetime must be a valid duration") + } else if lifetime < time.Minute || lifetime > time.Hour { + errs = append(errs, prefix+": tokenLifetime must be between 1m and 1h") + } + } + } else { + if c.TokenLifetime != "" { + errs = append(errs, prefix+": tokenLifetime is only supported for client_credentials clients") + } + // serviceSubject and roles are read only on the client_credentials + // path, so on a browser client they are silently ignored: the + // subject and roles come from the directory user. Rejecting them + // turns a registration that looks effective into a startup error + // (KEY-WP-0028). + // + // tenant is deliberately NOT in this list. A browser client may + // declare one, and humanTenant resolves it against the directory -- + // see docs/tenant-claim-contract.md, "How a human token's tenant is + // resolved". + if c.ServiceSubject != "" { + errs = append(errs, prefix+": serviceSubject is only read for client_credentials clients; a browser client's subject is the directory user") + } + if len(c.Roles) > 0 { + errs = append(errs, prefix+": roles is only read for client_credentials clients; a browser client's roles come from the directory user") + } + } + // Warn about wildcard redirect URIs (they are blocked at runtime anyway). + for _, uri := range c.RedirectURIs { + if strings.ContainsAny(uri, "*?") { + errs = append(errs, prefix+fmt.Sprintf(": redirect_uri %q must not contain wildcards", uri)) + } + } + if c.RegistrationURL != "" { + if err := validateHandoffURL(c.RegistrationURL); err != nil { + errs = append(errs, prefix+": registrationUrl: "+err.Error()) + } + } + if c.EnrollmentURL != "" { + if err := validateHandoffURL(c.EnrollmentURL); err != nil { + errs = append(errs, prefix+": enrollmentUrl: "+err.Error()) + } + } + } + + // Private key PEM path must be provided (existence is checked at startup). + if cfg.PrivateKeyPEM == "" { + errs = append(errs, "privateKeyPem: path must not be empty") + } + + return errs +} + +func contains(values []string, wanted string) bool { + for _, value := range values { + if value == wanted { + return true + } + } + return false +} + +func validateHandoffURL(raw string) error { + u, err := url.Parse(raw) + if err != nil || u.Scheme == "" || u.Host == "" { + return fmt.Errorf("%q is not an absolute URL", raw) + } + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("%q scheme must be http or https", raw) + } + if u.User != nil { + return fmt.Errorf("%q must not contain userinfo", raw) + } + if strings.ContainsAny(u.Host, "*?") || strings.ContainsAny(u.Path, "*") { + return fmt.Errorf("%q must not contain wildcards", raw) + } + return nil +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/discovery.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/discovery.go new file mode 100644 index 0000000..6291c51 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/discovery.go @@ -0,0 +1,142 @@ +// Package oidc implements OIDC profile endpoints for KeyCape. +// Only profile-supported features are advertised — no implicit flow, +// no dynamic registration, no request objects. +package oidc + +import ( + "encoding/json" + "net/http" + "sort" + + "keycape/internal/domain" +) + +// DiscoveryConfig holds the issuer and endpoint URLs for the discovery document. +// UserinfoEndpoint is optional; if empty it is omitted from the document. +type DiscoveryConfig struct { + Issuer string // e.g. "https://auth.netkingdom.local" + AuthorizationEndpoint string + TokenEndpoint string + JWKSUri string + UserinfoEndpoint string // optional, empty = not advertised + EndSessionEndpoint string // optional, empty = not advertised + IntrospectionEndpoint string // opt-in online access-token status + RevocationEndpoint string // opt-in access-token revocation + // Clients supplies the registered clients so scopes_supported reflects the + // scopes this deployment can actually grant, rather than a fixed list that + // omits every configured resource scope (KEY-WP-0017-T03). Nil or empty + // leaves only the baseline OIDC scopes advertised. + Clients map[string]*domain.Client +} + +// discoveryDocument is the JSON shape of /.well-known/openid-configuration. +// Fields are ordered to match common OIDC implementations for readability. +// registration_endpoint is intentionally absent — no dynamic client registration. +type discoveryDocument struct { + Issuer string `json:"issuer"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + JWKSUri string `json:"jwks_uri"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` + EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` + IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` + IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` + RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` + ResponseTypesSupported []string `json:"response_types_supported"` + GrantTypesSupported []string `json:"grant_types_supported"` + CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` + IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` + ScopesSupported []string `json:"scopes_supported"` + TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` + ClaimsSupported []string `json:"claims_supported"` + SubjectTypesSupported []string `json:"subject_types_supported"` + RequestParameterSupported bool `json:"request_parameter_supported"` + ClaimsParameterSupported bool `json:"claims_parameter_supported"` +} + +// discoveryHandler implements http.Handler for GET /.well-known/openid-configuration. +type discoveryHandler struct { + doc []byte +} + +// NewDiscoveryHandler returns an http.Handler that serves the OIDC discovery document. +// The document is pre-serialised at construction time so every request is a cheap copy. +func NewDiscoveryHandler(cfg DiscoveryConfig) http.Handler { + d := discoveryDocument{ + Issuer: cfg.Issuer, + AuthorizationEndpoint: cfg.AuthorizationEndpoint, + TokenEndpoint: cfg.TokenEndpoint, + JWKSUri: cfg.JWKSUri, + UserinfoEndpoint: cfg.UserinfoEndpoint, + EndSessionEndpoint: cfg.EndSessionEndpoint, + + // Profile-locked values — not negotiable. + ResponseTypesSupported: []string{"code"}, + GrantTypesSupported: []string{"authorization_code", "client_credentials"}, + CodeChallengeMethodsSupported: []string{"S256"}, + IDTokenSigningAlgValuesSupported: []string{"RS256"}, + ScopesSupported: supportedScopes(cfg.Clients), + TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "none"}, + // Core profile claims are emitted on every token, so omitting one here + // is a contract defect rather than missing optional metadata. The + // scope-gated human claims and the optional cached tenant_roles claim + // follow them. + ClaimsSupported: []string{ + "sub", "iss", "aud", "exp", "iat", + "tenant", "tenant_source", "principal_type", "roles", "groups", "assurance", "scope", + "nonce", "preferred_username", "email", "name", "tenant_roles", + }, + SubjectTypesSupported: []string{"public"}, + RequestParameterSupported: false, + ClaimsParameterSupported: false, + } + if cfg.IntrospectionEndpoint != "" { + d.IntrospectionEndpoint = cfg.IntrospectionEndpoint + d.IntrospectionEndpointAuthMethodsSupported = []string{"client_secret_basic"} + d.ClaimsSupported = append(d.ClaimsSupported, "jti") + } + if cfg.RevocationEndpoint != "" { + d.RevocationEndpoint = cfg.RevocationEndpoint + d.RevocationEndpointAuthMethodsSupported = []string{"client_secret_basic", "none"} + } + + b, err := json.Marshal(d) + if err != nil { + // This can only fail if the struct contains un-marshallable types, which it does not. + panic("oidc: failed to marshal discovery document: " + err.Error()) + } + return &discoveryHandler{doc: b} +} + +func (h *discoveryHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "max-age=3600") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(h.doc) +} + +// supportedScopes returns the baseline OIDC scopes plus every scope any +// registered client is allowed to request, sorted for a stable document. The +// baseline is advertised even with no clients configured, since it describes the +// profile rather than a particular deployment. +func supportedScopes(clients map[string]*domain.Client) []string { + seen := map[string]bool{} + scopes := []string{} + for _, scope := range []string{"openid", "profile", "email", "groups"} { + seen[scope] = true + scopes = append(scopes, scope) + } + extra := []string{} + for _, client := range clients { + for _, scope := range client.AllowedScopes { + if scope == "" || seen[scope] { + continue + } + seen[scope] = true + extra = append(extra, scope) + } + } + sort.Strings(extra) + return append(scopes, extra...) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token.go new file mode 100644 index 0000000..0ab2256 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token.go @@ -0,0 +1,625 @@ +package oidc + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "keycape/internal/adapters/tenantengine" + "keycape/internal/domain" + profileerrors "keycape/internal/errors" + "keycape/internal/server/telemetry" +) + +// TokenHandler implements POST /token. +type TokenHandler struct { + ClientConfig map[string]*domain.Client + Sessions *SessionStore + Users domain.UserRepository + SigningKey *rsa.PrivateKey + Issuer string + TokenLifetime time.Duration + Emitter telemetry.Emitter + // TenantEngine sources the optional tenant_roles claim (KEY-WP-0005-T02). + // Nil disables it entirely -- token issuance never depends on it. + TenantEngine *tenantengine.Client + // Tokens enables issuer-owned online token status; nil retains the legacy + // expiry-only profile. Enabling it requires explicit deployment admission. + Tokens *TokenStatusStore +} + +// tokenResponse is the JSON body returned on a successful token exchange. +type tokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + IDToken string `json:"id_token,omitempty"` +} + +// ServeHTTP handles POST /token. +func (h *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + if err := r.ParseForm(); err != nil { + http.Error(w, "invalid form body", http.StatusBadRequest) + return + } + + grantType := r.FormValue("grant_type") + if grantType == "client_credentials" { + h.serveClientCredentials(w, r) + return + } + clientID := r.FormValue("client_id") + code := r.FormValue("code") + codeVerifier := r.FormValue("code_verifier") + + // 1. Validate grant_type. + if grantType != "authorization_code" { + profileerrors.FeatureNotSupported( + "only grant_type=authorization_code is supported", + "grant_type="+grantType, + ).Write(w, http.StatusBadRequest) + return + } + + // 2. Validate client exists and may use this grant. + client, ok := h.ClientConfig[clientID] + if !ok { + profileerrors.InvalidProfileUsage("unknown client_id", "client_id"). + Write(w, http.StatusBadRequest) + return + } + + // Grant-type eligibility, enforced equivalently to the service path + // (KEY-WP-0016-T02). An empty grantTypes is an implicit authorization-code + // client, matching config validation; a client_credentials-only client must + // not reach the browser path. + if len(client.GrantTypes) > 0 && !containsString(client.GrantTypes, "authorization_code") { + profileerrors.InvalidProfileUsage( + "client is not registered for grant_type=authorization_code", + "grant_type", + ).Write(w, http.StatusBadRequest) + return + } + + // Confidential authorization-code clients authenticate with their secret, + // using the same credential sources as the service grant. A public client + // must not be able to present a secret and be treated as authenticated. + if client.ClientType == "confidential" { + presentedID, secret, ok := basicClientCredentials(r) + if !ok || presentedID != clientID || client.ClientSecret == "" || + !secretsEqual(secret, client.ClientSecret) { + profileerrors.InvalidProfileUsage( + "client authentication failed", + "Authorization", + ).Write(w, http.StatusUnauthorized) + return + } + } + + // 3. Consume the PKCE session. Single-use and atomic: see + // SessionStore.Consume (KEY-WP-0016-T01). + sess, ok := h.Sessions.Consume(code) + if !ok { + profileerrors.InvalidProfileUsage( + "authorization code not found or expired", + "code", + ).Write(w, http.StatusBadRequest) + return + } + + // Verify client_id matches the session. + if sess.ClientID != clientID { + profileerrors.InvalidProfileUsage( + "client_id does not match the authorization code", + "client_id", + ).Write(w, http.StatusBadRequest) + return + } + + // Bind the exchange to the redirect URI the code was issued for + // (RFC 6749 section 4.1.3, KEY-WP-0016-T02). /authorize always records an + // exactly-matched registered redirect, so the parameter is always required + // here and must be identical. + if redirectURI := r.FormValue("redirect_uri"); redirectURI != sess.RedirectURI { + profileerrors.InvalidProfileUsage( + "redirect_uri does not match the authorization request", + "redirect_uri", + ).Write(w, http.StatusBadRequest) + return + } + + // Recheck grants in case the client registration changed after authorization. + for _, scope := range sess.Scopes { + if !containsString(h.ClientConfig[clientID].AllowedScopes, scope) { + profileerrors.InvalidProfileUsage("requested scope is not allowed", "scope").Write(w, http.StatusBadRequest) + return + } + } + + // 4. Verify PKCE code_verifier. + if !verifyPKCE(codeVerifier, sess.PKCEChallenge) { + profileerrors.InvalidProfileUsage( + "code_verifier does not match code_challenge", + "code_verifier", + ).Write(w, http.StatusBadRequest) + return + } + + // 5. Look up user. + user, err := h.Users.LookupUser(ctx, sess.Username) + if err != nil { + http.Error(w, "user not found", http.StatusInternalServerError) + return + } + if isSuspended(user) { + profileerrors.RejectedForSafety( + "account is suspended", + "account_lifecycle", + ).Write(w, http.StatusForbidden) + return + } + + // 6. Build JWT claims. + now := time.Now() + exp := now.Add(h.TokenLifetime) + + claims := map[string]interface{}{ + "iss": h.Issuer, + "sub": user.ID, + "aud": clientID, + "exp": exp.Unix(), + "iat": now.Unix(), + } + if sess.Nonce != "" { + claims["nonce"] = sess.Nonce + } + + scopeSet := make(map[string]bool) + for _, s := range sess.Scopes { + scopeSet[s] = true + } + + if scopeSet["profile"] { + claims["preferred_username"] = user.Username + } + if scopeSet["email"] { + claims["email"] = user.Email + } + + // Core claims required by net-kingdom/canon/standards/iam-profile_v0.3.md + // for every production token -- not scope-gated, unlike the recommended + // human claims above (KEY-WP-0005-T01). + tenant, tenantSource, err := humanTenant(h.ClientConfig[clientID], user) + if err != nil { + profileerrors.RejectedForSafety( + "tenant binding conflict", + "tenant_binding", + ).Write(w, http.StatusForbidden) + return + } + claims["tenant"] = tenant + claims["tenant_source"] = tenantSource + claims["principal_type"] = "human" + claims["groups"] = nonNilStrings(user.Groups) + claims["roles"] = nonNilStrings(user.Roles) + claims["assurance"] = assuranceClaim(sess.MFAVerified, sess.AuthTime, now) + + // Optional cached tenant_roles claim (KEY-WP-0005-T02). Fails open -- + // see internal/adapters/tenantengine's package doc for why this is the + // one place in the whole tenant_roles design where that's correct. + if roles, ok := h.TenantEngine.Roles(ctx, tenant); ok { + claims["tenant_roles"] = roles + } + + // 7. Sign JWT with RSA-SHA256. + kid := "key-1" // static kid for v0.1 + jwtToken, err := buildJWT(claims, kid, h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return + } + + // Access tokens target the statically registered resource server. ID tokens + // remain bound to the OIDC relying party. + if audience := h.ClientConfig[clientID].Audience; audience != "" { + claims["aud"] = audience + } + claims["scope"] = strings.Join(sess.Scopes, " ") + if h.Tokens != nil { + id := make([]byte, 32) + if _, err := rand.Read(id); err != nil { + http.Error(w, "token status unavailable", http.StatusServiceUnavailable) + return + } + claims["jti"] = base64.RawURLEncoding.EncodeToString(id) + } + accessToken, err := buildJWT(claims, kid, h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return + } + if h.Tokens != nil { + if err := h.Tokens.register(accessToken, client, time.Unix(exp.Unix(), 0), now); err != nil { + http.Error(w, "token status unavailable", http.StatusServiceUnavailable) + return + } + } + + // 8. Build response. The session was already consumed at lookup, so no + // separate replay-prevention delete is needed here. + resp := tokenResponse{ + AccessToken: accessToken, + TokenType: "Bearer", + ExpiresIn: int(h.TokenLifetime.Seconds()), + IDToken: jwtToken, + } + + // 10. Emit token_issued telemetry. + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: time.Now(), + EventType: telemetry.EventTokenIssued, + ClientID: clientID, + Endpoint: "/token", + Result: "success", + Scopes: sess.Scopes, + GrantType: grantType, + }) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} + +// basicClientCredentials reads client_secret_basic credentials, applying the +// form-encoding decode RFC 6749 appendix B requires of both halves. Shared by +// the service grant and confidential authorization-code client authentication. +func basicClientCredentials(r *http.Request) (clientID, clientSecret string, ok bool) { + clientID, clientSecret, ok = r.BasicAuth() + if !ok { + return "", "", false + } + decodedID, idErr := url.QueryUnescape(clientID) + decodedSecret, secretErr := url.QueryUnescape(clientSecret) + if idErr != nil || secretErr != nil { + return "", "", false + } + return decodedID, decodedSecret, true +} + +// secretsEqual compares two secrets in constant time. Digesting first keeps the +// comparison length-independent, so a wrong-length secret is indistinguishable +// from a wrong-value one. +func secretsEqual(presented, expected string) bool { + presentedDigest := sha256.Sum256([]byte(presented)) + expectedDigest := sha256.Sum256([]byte(expected)) + return subtle.ConstantTimeCompare(presentedDigest[:], expectedDigest[:]) == 1 +} + +func (h *TokenHandler) serveClientCredentials(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + clientID, clientSecret, ok := r.BasicAuth() + if !ok { + profileerrors.InvalidProfileUsage("client_secret_basic authentication required", "Authorization"). + Write(w, http.StatusUnauthorized) + return + } + clientID, idErr := url.QueryUnescape(clientID) + clientSecret, secretErr := url.QueryUnescape(clientSecret) + if idErr != nil || secretErr != nil { + profileerrors.InvalidProfileUsage("invalid client authentication encoding", "Authorization").Write(w, http.StatusUnauthorized) + return + } + client, ok := h.ClientConfig[clientID] + if !ok || client.ClientType != "confidential" || !containsString(client.GrantTypes, "client_credentials") { + profileerrors.InvalidProfileUsage("invalid confidential client", "client_id"). + Write(w, http.StatusUnauthorized) + return + } + presentedDigest := sha256.Sum256([]byte(clientSecret)) + expectedDigest := sha256.Sum256([]byte(client.ClientSecret)) + if client.ClientSecret == "" || + subtle.ConstantTimeCompare(presentedDigest[:], expectedDigest[:]) != 1 { + profileerrors.InvalidProfileUsage("invalid client authentication", "Authorization"). + Write(w, http.StatusUnauthorized) + return + } + + requestedScopes := strings.Fields(r.FormValue("scope")) + if len(requestedScopes) == 0 { + requestedScopes = append([]string(nil), client.AllowedScopes...) + } + for _, scope := range requestedScopes { + if !containsString(client.AllowedScopes, scope) { + profileerrors.InvalidProfileUsage("requested scope is not allowed", "scope"). + Write(w, http.StatusBadRequest) + return + } + } + + now := time.Now() + tokenLifetime := h.TokenLifetime + if client.TokenLifetime > 0 { + tokenLifetime = client.TokenLifetime + } + claims := map[string]interface{}{ + "iss": h.Issuer, + "sub": client.ServiceSubject, + "aud": accessAudience(client), + "exp": now.Add(tokenLifetime).Unix(), + "iat": now.Unix(), + "tenant": client.Tenant, + // A service client's tenant is always registration-supplied: there is no + // directory principal behind it to assert one (GH-DEC-2026-013 §5). + "tenant_source": TenantSourceRegistration, + "principal_type": "service", + "groups": []string{}, + "roles": nonNilStrings(client.Roles), + "scope": strings.Join(requestedScopes, " "), + "assurance": map[string]interface{}{ + "level": "aal1", "methods": []string{"client_secret"}, + "mfa": false, "source": "key-cape", "at": now.Unix(), + }, + } + if roles, ok := h.TenantEngine.Roles(ctx, client.Tenant); ok { + claims["tenant_roles"] = roles + } + if h.Tokens != nil { + id := make([]byte, 32) + if _, err := rand.Read(id); err != nil { + http.Error(w, "token status unavailable", http.StatusServiceUnavailable) + return + } + claims["jti"] = base64.RawURLEncoding.EncodeToString(id) + } + jwtToken, err := buildJWT(claims, "key-1", h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return + } + if h.Tokens != nil { + if err := h.Tokens.register(jwtToken, client, time.Unix(now.Add(tokenLifetime).Unix(), 0), now); err != nil { + http.Error(w, "token status unavailable", http.StatusServiceUnavailable) + return + } + } + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: now, EventType: telemetry.EventTokenIssued, ClientID: clientID, + Endpoint: "/token", Result: "success", Scopes: requestedScopes, + GrantType: "client_credentials", + }) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(tokenResponse{ + AccessToken: jwtToken, TokenType: "Bearer", + ExpiresIn: int(tokenLifetime.Seconds()), + }) +} + +func containsString(values []string, wanted string) bool { + for _, value := range values { + if value == wanted { + return true + } + } + return false +} + +func isSuspended(user *domain.User) bool { + return containsString(user.Groups, "netkingdom-suspended") +} + +// --------------------------------------------------------------------------- +// IAM Profile core claims (KEY-WP-0005-T01) +// --------------------------------------------------------------------------- + +// defaultTenant is the fallback tenant claim for users with no explicit +// Tenant assignment yet. This workstation currently operates a single +// tenant (tenant:coulomb, ADR-0006); later tenants (e.g. tenant:friendly:binky, +// ADR-0013) require an explicit domain.User.Tenant value -- this default +// never silently assigns a user to a tenant other than the platform's +// original one. +const defaultTenant = "tenant:coulomb" + +// effectiveTenant resolves the tenant claim for a user, falling back to +// defaultTenant when the user has no explicit tenant assignment. The IAM +// Profile requires a non-empty tenant claim on every token. +func effectiveTenant(user *domain.User) string { + if user.Tenant != "" { + return user.Tenant + } + return defaultTenant +} + +// humanTenant resolves the tenant claim for a human token (KEY-WP-0013-T05). +// +// A human's tenant is normally a property of the person, read from the +// directory. But the approval chain is bound to the landlord zone by decision +// 5ed3fb35-eca9-413a-82b9-95171ba85bf6, and approval-engine compares the claim +// by exact string equality, so an approver client has to be able to state the +// zone it issues into. Without this every human token fell back to +// defaultTenant and would have been refused downstream -- as a failed approval +// rather than as a registration defect. +// +// The rule is deliberately not an override: +// +// - no client tenant declared -> the directory answer, unchanged; +// - declared, user unassigned -> the declared zone; +// - declared and equal -> agreement, no ambiguity; +// - declared and different -> refuse to issue. +// +// So a registration can bind a zone for users the directory has not placed, and +// can never relabel a user the directory HAS placed into a different one. That +// last case fails closed rather than picking a winner, because either answer +// would be a silent cross-tenant assertion. It also means this stays correct if +// the directory later populates Tenant: the same code turns from supplying the +// zone into enforcing agreement with it, with no second migration. +// +// This is only safe because client registrations are static and +// deployment-owned; KeyCape excludes dynamic client registration by design. A +// self-service client that could name its users' tenant would be an escalation. +func humanTenant(client *domain.Client, user *domain.User) (string, string, error) { + if client == nil || client.Tenant == "" { + if user.Tenant != "" { + return user.Tenant, TenantSourceDirectory, nil + } + return defaultTenant, TenantSourceDefault, nil + } + if user.Tenant != "" { + if user.Tenant != client.Tenant { + return "", "", fmt.Errorf("client %q binds tenant %q but the directory assigns this user a different tenant", client.ClientID, client.Tenant) + } + // Agreement: the directory did assert this about the person, so the + // stronger provenance is the true one. + return user.Tenant, TenantSourceDirectory, nil + } + return client.Tenant, TenantSourceRegistration, nil +} + +// Tenant provenance values for the tenant_source claim (GH-DEC-2026-013 §5). +// +// A bare tenant string cannot tell a consumer whether the identity layer +// asserted the zone about this PERSON or a registration supplied it about the +// CLIENT they came through. approval-engine admits an approver by exact-matching +// that string while its contract reads as though it relies on the first, so the +// check is sound and the property a reader infers from it is absent. The ruling +// requires the claim to carry its provenance and forbids a consumer treating the +// two as equivalent for any decision turning on a fact about the person. It names +// the property; the field is ours. +// +// Three values, not the two the ruling names, and the third is the point. A +// tenant nobody asserted -- neither directory nor registration, just the +// profile's non-empty default -- is not directory-asserted, and labelling it so +// would reintroduce the same defect one level down: a consumer would read +// "directory" for a fact the directory never stated. That is the unknown-versus- +// absent distinction the ruling cites from GH-DEC-2026-011 §3, applied to our own +// fallback rather than only to the case we were asked about. +const ( + // TenantSourceDirectory: the identity layer asserted this zone about this + // person. Includes the agreement case, where a registration declared the + // same zone the directory did -- the directory still asserted it. + TenantSourceDirectory = "directory" + + // TenantSourceRegistration: supplied by the client registration for a person + // the directory has placed nowhere. A fact about the client, not the person. + TenantSourceRegistration = "registration" + + // TenantSourceDefault: nobody asserted a zone; this is the profile default, + // emitted because the profile requires a non-empty tenant. Weaker than both. + TenantSourceDefault = "default" +) + +// nonNilStrings returns s, or an empty (non-nil) slice if s is nil, so the +// claim always serializes as `[]`, never `null` -- the profile requires +// groups/roles to be present, "possibly empty", not absent. +func nonNilStrings(s []string) []string { + if s == nil { + return []string{} + } + return s +} + +// assuranceClaim builds the profile's `assurance` object from whether MFA +// was actually verified during this authorization (session.MFAVerified), +// not from static enrollment state -- a user who has MFA enrolled but +// wasn't challenged for it in this particular flow gets aal1, not aal2. +// +// `at` is the time the user authenticated, not the time this token was +// minted. Those differ whenever a browser session is reused, and the gap is +// the whole point: approval-engine stores this object verbatim as the only +// downstream record that MFA occurred (KEY-WP-0013-T05), so mint time would +// overstate how recently the person proved anything. issuedAt is the +// fallback for a session predating this field. +func assuranceClaim(mfaVerified bool, authTime, issuedAt time.Time) map[string]interface{} { + at := authTime + if at.IsZero() { + at = issuedAt + } + level := "aal1" + methods := []string{"pwd"} + if mfaVerified { + level = "aal2" + methods = append(methods, "otp") + } + return map[string]interface{}{ + "level": level, + "methods": methods, + "mfa": mfaVerified, + "source": "key-cape", + "at": at.Unix(), + } +} + +// --------------------------------------------------------------------------- +// PKCE verification +// --------------------------------------------------------------------------- + +// verifyPKCE checks BASE64URL(SHA256(verifier)) == challenge (S256 method). +func verifyPKCE(verifier, challenge string) bool { + h := sha256.New() + h.Write([]byte(verifier)) + computed := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) + return computed == challenge +} + +// --------------------------------------------------------------------------- +// JWT construction (stdlib only — no external JWT library) +// --------------------------------------------------------------------------- + +type jwtHeader struct { + Alg string `json:"alg"` + Typ string `json:"typ"` + Kid string `json:"kid"` +} + +// buildJWT constructs and signs a JWT using RSA-SHA256 with the standard library. +// Format: base64url(header) + "." + base64url(payload) + "." + base64url(signature) +func buildJWT(claims map[string]interface{}, kid string, key *rsa.PrivateKey) (string, error) { + // Header. + hdr := jwtHeader{Alg: "RS256", Typ: "JWT", Kid: kid} + hdrJSON, err := json.Marshal(hdr) + if err != nil { + return "", err + } + hdrB64 := base64.RawURLEncoding.EncodeToString(hdrJSON) + + // Payload. + payloadJSON, err := json.Marshal(claims) + if err != nil { + return "", err + } + payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON) + + // Signing input. + signingInput := hdrB64 + "." + payloadB64 + + // Digest. + digest := sha256.Sum256([]byte(signingInput)) + + // Sign with PKCS1v15 / SHA256. + sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, digest[:]) + if err != nil { + return "", err + } + sigB64 := base64.RawURLEncoding.EncodeToString(sig) + + return strings.Join([]string{hdrB64, payloadB64, sigB64}, "."), nil +} + +// accessAudience is configured by the issuer, never selected by request input. +func accessAudience(client *domain.Client) string { + if client.Audience != "" { + return client.Audience + } + return client.ClientID +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status.go new file mode 100644 index 0000000..34792f4 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status.go @@ -0,0 +1,97 @@ +package oidc + +import ( + "crypto/sha256" + "encoding/json" + "errors" + "sync" + "time" + + "keycape/internal/domain" +) + +// TokenStatusStore is the optional single-process online access-token ledger. +// It holds only token hashes and registration commitments, never bearer values. +// Losing this store makes every previously issued token inactive to online +// checks. It is not a browser session, approval, or operation authority store. +type TokenStatusStore struct { + mu sync.Mutex + entries map[[32]byte]tokenStatus + limit int +} + +type tokenStatus struct { + clientID string + registration [32]byte + expires time.Time + revoked bool +} + +func NewTokenStatusStore(limit int) (*TokenStatusStore, error) { + if limit < 1 || limit > 1000000 { + return nil, errors.New("invalid token-status capacity") + } + return &TokenStatusStore{entries: make(map[[32]byte]tokenStatus), limit: limit}, nil +} + +func registrationCommitment(client *domain.Client) [32]byte { + // ClientSecret and TokenLifetime are deliberately omitted from Client JSON. + // Bind their commitments explicitly as well as all public registration data. + secret := sha256.Sum256([]byte(client.ClientSecret)) + raw, _ := json.Marshal(struct { + Client *domain.Client + Secret [32]byte + Lifetime time.Duration + }{client, secret, client.TokenLifetime}) + return sha256.Sum256(raw) +} + +func (s *TokenStatusStore) register(raw string, client *domain.Client, expires, now time.Time) error { + if s == nil || client == nil || raw == "" || len(raw) > 32768 || !now.Before(expires) { + return errors.New("token-status registration refused") + } + s.mu.Lock() + defer s.mu.Unlock() + for key, entry := range s.entries { + if !now.Before(entry.expires) { + delete(s.entries, key) + } + } + key := sha256.Sum256([]byte(raw)) + if _, exists := s.entries[key]; exists || len(s.entries) >= s.limit { + return errors.New("token-status registration refused") + } + s.entries[key] = tokenStatus{clientID: client.ClientID, registration: registrationCommitment(client), expires: expires} + return nil +} + +func (s *TokenStatusStore) active(raw string, clients map[string]*domain.Client, now time.Time) (string, bool) { + if s == nil { + return "", false + } + s.mu.Lock() + defer s.mu.Unlock() + entry, ok := s.entries[sha256.Sum256([]byte(raw))] + if !ok || entry.revoked || !now.Before(entry.expires) { + return "", false + } + client := clients[entry.clientID] + if client == nil || client.ClientID != entry.clientID || registrationCommitment(client) != entry.registration { + return "", false + } + return entry.clientID, true +} + +func (s *TokenStatusStore) revoke(raw, clientID string) { + if s == nil { + return + } + s.mu.Lock() + defer s.mu.Unlock() + key := sha256.Sum256([]byte(raw)) + if entry, ok := s.entries[key]; ok && entry.clientID == clientID { + // Retain the tombstone until expiry: a retry can never reactivate it. + entry.revoked = true + s.entries[key] = entry + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_discovery_test.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_discovery_test.go new file mode 100644 index 0000000..bf8ca62 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_discovery_test.go @@ -0,0 +1,30 @@ +package oidc_test + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "keycape/internal/server/oidc" +) + +func TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile(t *testing.T) { + for _, enabled := range []bool{false, true} { + cfg := oidc.DiscoveryConfig{Issuer: "https://issuer.example"} + if enabled { + cfg.IntrospectionEndpoint, cfg.RevocationEndpoint = cfg.Issuer+"/introspect", cfg.Issuer+"/revoke" + } + w := httptest.NewRecorder() + oidc.NewDiscoveryHandler(cfg).ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/.well-known/openid-configuration", nil)) + var doc map[string]interface{} + if json.Unmarshal(w.Body.Bytes(), &doc) != nil { + t.Fatal("discovery is invalid JSON") + } + for _, name := range []string{"introspection_endpoint", "revocation_endpoint", "introspection_endpoint_auth_methods_supported", "revocation_endpoint_auth_methods_supported"} { + if _, exists := doc[name]; exists != enabled { + t.Fatal("unimplemented token status advertised") + } + } + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_http.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_http.go new file mode 100644 index 0000000..5bd9161 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_http.go @@ -0,0 +1,145 @@ +package oidc + +import ( + "crypto/rsa" + "encoding/json" + "mime" + "net/http" + "time" + + "keycape/internal/domain" +) + +// TokenStatusHandler serves the optional issuer-owned RFC 7662 introspection +// and RFC 7009 revocation endpoints. Introspection authenticates the issuing +// confidential client; this profile does not delegate cross-client token reads. +type TokenStatusHandler struct { + Tokens *TokenStatusStore + ClientConfig map[string]*domain.Client + Users domain.UserRepository + SigningKey *rsa.PublicKey + Issuer string +} + +func statusError(w http.ResponseWriter, status int, code string) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Pragma", "no-cache") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(map[string]string{"error": code}) +} + +func (h *TokenStatusHandler) parse(w http.ResponseWriter, r *http.Request, introspection bool) (*domain.Client, string, bool) { + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Pragma", "no-cache") + if r.Method != http.MethodPost { + w.Header().Set("Allow", "POST") + statusError(w, http.StatusMethodNotAllowed, "invalid_request") + return nil, "", false + } + if h.Tokens == nil || h.SigningKey == nil || h.Issuer == "" { + statusError(w, http.StatusServiceUnavailable, "temporarily_unavailable") + return nil, "", false + } + media, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil || media != "application/x-www-form-urlencoded" || r.URL.RawQuery != "" || len(r.Header.Values("Authorization")) > 1 { + statusError(w, http.StatusBadRequest, "invalid_request") + return nil, "", false + } + r.Body = http.MaxBytesReader(w, r.Body, 65536) + if err := r.ParseForm(); err != nil { + statusError(w, http.StatusBadRequest, "invalid_request") + return nil, "", false + } + for _, values := range r.PostForm { + if len(values) != 1 { + statusError(w, http.StatusBadRequest, "invalid_request") + return nil, "", false + } + } + raw := r.PostForm.Get("token") + if raw == "" || len(raw) > 32768 { + statusError(w, http.StatusBadRequest, "invalid_request") + return nil, "", false + } + id, secret, basic := basicClientCredentials(r) + formID := r.PostForm.Get("client_id") + if !basic { + id = formID + } + client := h.ClientConfig[id] + valid := client != nil && client.ClientID == id && !r.PostForm.Has("client_secret") + if valid && client.ClientType == "confidential" { + valid = basic && (formID == "" || formID == id) && client.ClientSecret != "" && secretsEqual(secret, client.ClientSecret) + } else { + // Public-client revocation requires possession of its own bearer token. + // Public clients can never enumerate token status via introspection. + valid = valid && !introspection && client.ClientType == "public" && !basic && r.Header.Get("Authorization") == "" + } + if !valid { + w.Header().Set("WWW-Authenticate", `Basic realm="keycape-token-status"`) + statusError(w, http.StatusUnauthorized, "invalid_client") + return nil, "", false + } + return client, raw, true +} + +func (h *TokenStatusHandler) Introspect(w http.ResponseWriter, r *http.Request) { + client, raw, ok := h.parse(w, r, true) + if !ok { + return + } + response := map[string]interface{}{"active": false} + defer func() { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) + }() + id, active := h.Tokens.active(raw, h.ClientConfig, time.Now()) + if !active || id != client.ClientID { + return + } + claims, err := validateAccessToken(raw, h.SigningKey, h.Issuer) + if err != nil { + return + } + sub, _ := claims["sub"].(string) + switch claims["principal_type"] { + case "human": + if h.Users == nil { + return + } + user, err := lookupUserBySubject(r.Context(), h.Users, claims, sub) + if err != nil || !user.Enabled || isSuspended(user) { + return + } + case "service": + if sub == "" || sub != client.ServiceSubject { + return + } + default: + return + } + // Directory lookup may block. Recheck revocation and registration after it. + if current, active := h.Tokens.active(raw, h.ClientConfig, time.Now()); !active || current != id { + return + } + response["active"] = true + response["client_id"] = id + response["token_type"] = "Bearer" + for _, field := range []string{"iss", "sub", "aud", "scope", "iat", "exp", "jti"} { + if value, present := claims[field]; present { + response[field] = value + } + } +} + +func (h *TokenStatusHandler) Revoke(w http.ResponseWriter, r *http.Request) { + client, raw, ok := h.parse(w, r, false) + if !ok { + return + } + // Unknown, expired, ID and other-client tokens receive the same empty + // success as an already-revoked token; no existence information leaks. + h.Tokens.revoke(raw, client.ClientID) + w.WriteHeader(http.StatusOK) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_internal_test.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_internal_test.go new file mode 100644 index 0000000..3fe725c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_internal_test.go @@ -0,0 +1,66 @@ +package oidc + +import ( + "fmt" + "sync" + "testing" + "time" + + "keycape/internal/domain" +) + +func TestTokenStatusExactExpiryCapacityAndTombstones(t *testing.T) { + client := &domain.Client{ClientID: "synthetic-client", ClientType: "public"} + clients := map[string]*domain.Client{client.ClientID: client} + store, _ := NewTokenStatusStore(1) + now := time.Unix(1700000000, 0) + end := now.Add(time.Second) + if store.register("synthetic-first", client, end, now) != nil { + t.Fatal("register refused") + } + if _, active := store.active("synthetic-first", clients, end); active { + t.Fatal("expiry equality accepted") + } + store.revoke("synthetic-first", client.ClientID) + if store.register("synthetic-first", client, end, now) == nil { + t.Fatal("revoked token reactivated") + } + if store.register("synthetic-second", client, end.Add(time.Second), end) != nil { + t.Fatal("expired tombstone not collected") + } + if _, active := store.active("synthetic-second", clients, end); !active { + t.Fatal("fresh token refused") + } + for _, capacity := range []int{-1, 0, 1000001} { + if _, err := NewTokenStatusStore(capacity); err == nil { + t.Fatal("unbounded capacity accepted") + } + } +} + +func TestTokenStatusConcurrentIssueReadAndRevoke(t *testing.T) { + store, _ := NewTokenStatusStore(128) + client := &domain.Client{ClientID: "synthetic-client", ClientType: "public"} + clients := map[string]*domain.Client{client.ClientID: client} + now := time.Now() + var wg sync.WaitGroup + for i := 0; i < 64; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + raw := fmt.Sprintf("synthetic-token-%d", i) + if store.register(raw, client, now.Add(time.Minute), now) != nil { + t.Error("concurrent registration failed") + return + } + if _, active := store.active(raw, clients, now); !active { + t.Error("concurrent current token refused") + } + store.revoke(raw, client.ClientID) + if _, active := store.active(raw, clients, now); active { + t.Error("concurrent revoked token accepted") + } + }(i) + } + wg.Wait() +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_test.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_test.go new file mode 100644 index 0000000..deb3f52 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_test.go @@ -0,0 +1,379 @@ +package oidc_test + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + "keycape/internal/domain" + "keycape/internal/server/oidc" + "keycape/internal/server/telemetry" +) + +type statusFixture struct { + token *oidc.TokenHandler + status *oidc.TokenStatusHandler + userinfo *oidc.UserinfoHandler + users *mockUserRepo +} + +func newStatusFixture(t *testing.T, capacity int) *statusFixture { + t.Helper() + users := &mockUserRepo{users: map[string]*domain.User{"alice": aliceUser()}} + tokens, err := oidc.NewTokenStatusStore(capacity) + if err != nil { + t.Fatal("store creation failed") + } + h, key := newTokenHandler(t, oidc.NewSessionStore(), users) + h.Tokens = tokens + h.ClientConfig["test-client"].ClientType = "confidential" + h.ClientConfig["test-client"].ClientSecret = "synthetic-status-only" + h.ClientConfig["other"] = &domain.Client{ClientID: "other", ClientType: "confidential", ClientSecret: "synthetic-other-only"} + return &statusFixture{token: h, users: users, + status: &oidc.TokenStatusHandler{Tokens: tokens, ClientConfig: h.ClientConfig, Users: users, SigningKey: &key.PublicKey, Issuer: h.Issuer}, + userinfo: &oidc.UserinfoHandler{Tokens: tokens, ClientConfig: h.ClientConfig, Users: users, SigningKey: &key.PublicKey, Issuer: h.Issuer, Emitter: telemetry.NoopEmitter{}}, + } +} + +func (f *statusFixture) issue(t *testing.T) (string, string, int) { + t.Helper() + verifier, _ := makeVerifierAndChallenge() + code := seededSession(f.token.Sessions, verifier) + req := tokenRequest(url.Values{"grant_type": {"authorization_code"}, "client_id": {"test-client"}, "code": {code}, "code_verifier": {verifier}}) + if f.token.ClientConfig["test-client"].ClientType == "confidential" { + req.SetBasicAuth(url.QueryEscape("test-client"), url.QueryEscape(f.token.ClientConfig["test-client"].ClientSecret)) + } + w := httptest.NewRecorder() + f.token.ServeHTTP(w, req) + if w.Code != http.StatusOK { + return "", "", w.Code + } + var body struct { + Access string `json:"access_token"` + ID string `json:"id_token"` + } + if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Access == "" || body.ID == "" { + t.Fatal("invalid token response") + } + return body.Access, body.ID, w.Code +} + +func (f *statusFixture) request(t *testing.T, raw, client, secret string, revoke bool, mutate func(*http.Request)) *httptest.ResponseRecorder { + t.Helper() + form := url.Values{"token": {raw}} + if secret == "" { + form.Set("client_id", client) + } + r := httptest.NewRequest(http.MethodPost, "/introspect", strings.NewReader(form.Encode())) + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if secret != "" { + r.SetBasicAuth(url.QueryEscape(client), url.QueryEscape(secret)) + } + if mutate != nil { + mutate(r) + } + w := httptest.NewRecorder() + if revoke { + f.status.Revoke(w, r) + } else { + f.status.Introspect(w, r) + } + if w.Header().Get("Cache-Control") != "no-store" || w.Header().Get("Pragma") != "no-cache" { + t.Fatal("missing private response controls") + } + return w +} + +func (f *statusFixture) introspect(t *testing.T, raw string) map[string]interface{} { + t.Helper() + w := f.request(t, raw, "test-client", f.token.ClientConfig["test-client"].ClientSecret, false, nil) + if w.Code != http.StatusOK { + t.Fatalf("introspection status %d", w.Code) + } + var body map[string]interface{} + if json.Unmarshal(w.Body.Bytes(), &body) != nil { + t.Fatal("invalid introspection response") + } + return body +} + +func (f *statusFixture) userinfoStatus(raw string) int { + w := httptest.NewRecorder() + f.userinfo.ServeHTTP(w, userinfoRequest(raw)) + return w.Code +} + +func TestTokenStatusActualIssueRevokeAndFreshGrant(t *testing.T) { + f := newStatusFixture(t, 8) + first, id, code := f.issue(t) + if code != http.StatusOK { + t.Fatalf("issue status %d", code) + } + active := f.introspect(t, first) + if active["active"] != true || active["sub"] != "user-alice" || active["client_id"] != "test-client" || active["token_type"] != "Bearer" { + t.Fatal("wrong active-token metadata") + } + if _, present := parseJWTPayload(t, id)["jti"]; present { + t.Fatal("access-token lifecycle changed ID token") + } + if f.userinfoStatus(first) != http.StatusOK { + t.Fatal("issued token refused") + } + second, _, code := f.issue(t) + if code != http.StatusOK || first == second || active["jti"] == f.introspect(t, second)["jti"] { + t.Fatal("fresh grants must be distinct") + } + for i := 0; i < 2; i++ { + w := f.request(t, first, "test-client", "synthetic-status-only", true, nil) + if w.Code != http.StatusOK || w.Body.Len() != 0 { + t.Fatal("revocation must be empty idempotent success") + } + } + if got := f.introspect(t, first); got["active"] != false || len(got) != 1 { + t.Fatal("revoked metadata leaked or remained active") + } + if f.userinfoStatus(first) != http.StatusUnauthorized { + t.Fatal("revoked token still accepted by real UserInfo") + } + if f.introspect(t, second)["active"] != true || f.userinfoStatus(second) != http.StatusOK { + t.Fatal("revoking one grant affected another") + } + third, _, code := f.issue(t) + if code != http.StatusOK || f.introspect(t, third)["active"] != true || f.introspect(t, first)["active"] != false { + t.Fatal("fresh issuance reactivated a revoked grant") + } +} + +func TestTokenStatusNoCrossClientOrIDTokenAuthority(t *testing.T) { + f := newStatusFixture(t, 8) + raw, id, _ := f.issue(t) + for _, invalid := range []string{id, "unknown.synthetic.token", raw + "changed"} { + got := f.introspect(t, invalid) + if len(got) != 1 || got["active"] != false { + t.Fatal("non-access token disclosed metadata") + } + w := f.request(t, invalid, "test-client", "synthetic-status-only", true, nil) + if w.Code != http.StatusOK || w.Body.Len() != 0 { + t.Fatal("unknown revocation disclosed state") + } + } + w := f.request(t, raw, "other", "synthetic-other-only", false, nil) + if w.Code != http.StatusOK || strings.TrimSpace(w.Body.String()) != `{"active":false}` { + t.Fatal("other client read token") + } + w = f.request(t, raw, "other", "synthetic-other-only", true, nil) + if w.Code != http.StatusOK || f.introspect(t, raw)["active"] != true { + t.Fatal("other client revoked token") + } +} + +func TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken(t *testing.T) { + f := newStatusFixture(t, 1) + raw, _, code := f.issue(t) + if code != http.StatusOK { + t.Fatal("first issuance failed") + } + if _, _, code = f.issue(t); code != http.StatusServiceUnavailable { + t.Fatalf("full store issue status %d", code) + } + if f.introspect(t, raw)["active"] != true { + t.Fatal("capacity evicted live authority") + } + f.request(t, raw, "test-client", "synthetic-status-only", true, nil) + if _, _, code = f.issue(t); code != http.StatusServiceUnavailable { + t.Fatal("revocation tombstone was evicted before expiry") + } +} + +func TestTokenStatusRestartAndUnavailableStoreRefuse(t *testing.T) { + f := newStatusFixture(t, 8) + raw, _, _ := f.issue(t) + empty, err := oidc.NewTokenStatusStore(8) + if err != nil { + t.Fatal(err) + } + f.status.Tokens, f.userinfo.Tokens = empty, empty + if f.introspect(t, raw)["active"] != false || f.userinfoStatus(raw) != http.StatusUnauthorized { + t.Fatal("restart resurrected unknown token") + } + f.status.Tokens = nil + if w := f.request(t, raw, "test-client", "synthetic-status-only", false, nil); w.Code != http.StatusServiceUnavailable { + t.Fatal("missing store allowed online status") + } + // Offline JWT validity remains a separate legacy contract. Do not claim + // that adding an endpoint revokes tokens in consumers that never consult it. + f.userinfo.Tokens = nil + if f.userinfoStatus(raw) != http.StatusOK { + t.Fatal("legacy profile changed") + } +} + +func TestTokenStatusRegistrationDriftAndDirectoryDisposition(t *testing.T) { + for _, change := range []string{"removed", "audience", "secret", "scopes", "lifetime", "suspended", "disabled", "user-removed"} { + t.Run(change, func(t *testing.T) { + f := newStatusFixture(t, 8) + raw, _, _ := f.issue(t) + switch change { + case "removed": + delete(f.token.ClientConfig, "test-client") + case "audience": + f.token.ClientConfig["test-client"].Audience = "different-resource" + case "secret": + f.token.ClientConfig["test-client"].ClientSecret = "replacement-synthetic-only" + case "scopes": + f.token.ClientConfig["test-client"].AllowedScopes = []string{"openid"} + case "lifetime": + f.token.ClientConfig["test-client"].TokenLifetime = time.Minute + case "suspended": + f.users.users["alice"].Groups = []string{"netkingdom-suspended"} + case "disabled": + f.users.users["alice"].Enabled = false + case "user-removed": + delete(f.users.users, "alice") + } + if f.userinfoStatus(raw) != http.StatusUnauthorized { + t.Fatal("UserInfo accepted changed authority") + } + if change != "removed" && f.introspect(t, raw)["active"] != false { + t.Fatal("introspection accepted changed authority") + } + }) + } +} + +func TestTokenStatusPublicClientMayRevokeButNotIntrospect(t *testing.T) { + f := newStatusFixture(t, 8) + f.token.ClientConfig["test-client"].ClientType = "public" + f.token.ClientConfig["test-client"].ClientSecret = "" + raw, _, code := f.issue(t) + if code != http.StatusOK { + t.Fatal("public PKCE issue failed") + } + if w := f.request(t, raw, "test-client", "", false, nil); w.Code != http.StatusUnauthorized { + t.Fatal("public client enumerated status") + } + if w := f.request(t, raw, "test-client", "", true, nil); w.Code != http.StatusOK { + t.Fatal("public bearer revocation failed") + } + if f.userinfoStatus(raw) != http.StatusUnauthorized { + t.Fatal("public revocation not enforced") + } +} + +func TestTokenStatusServiceGrantIsCurrentAndDistinct(t *testing.T) { + f := newStatusFixture(t, 8) + client := f.token.ClientConfig["test-client"] + client.GrantTypes, client.ServiceSubject, client.Tenant = []string{"client_credentials"}, "service:status-fixture", "tenant:fixture" + issue := func() string { + r := tokenRequest(url.Values{"grant_type": {"client_credentials"}}) + r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret)) + w := httptest.NewRecorder() + f.token.ServeHTTP(w, r) + if w.Code != http.StatusOK { + t.Fatalf("service issue status %d", w.Code) + } + var body struct { + Access string `json:"access_token"` + } + if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Access == "" { + t.Fatal("service response invalid") + } + return body.Access + } + first, second := issue(), issue() + if first == second || f.introspect(t, first)["active"] != true { + t.Fatal("service grants not distinct/current") + } + f.request(t, first, client.ClientID, client.ClientSecret, true, nil) + if f.introspect(t, first)["active"] != false || f.introspect(t, second)["active"] != true { + t.Fatal("service revocation not token-scoped") + } +} + +func TestTokenStatusRejectsMalformedOrUnauthenticatedRequests(t *testing.T) { + for _, kind := range []string{"get", "query", "json", "duplicate-token", "oversize", "wrong-secret", "missing-auth", "duplicate-auth", "body-secret", "mismatched-client"} { + t.Run(kind, func(t *testing.T) { + f := newStatusFixture(t, 8) + raw, _, _ := f.issue(t) + mutate := func(r *http.Request) { + body := func(form url.Values) { r.Body = io.NopCloser(strings.NewReader(form.Encode())) } + switch kind { + case "get": + r.Method = http.MethodGet + case "query": + r.URL.RawQuery = "token=must-never-use-url" + case "json": + r.Header.Set("Content-Type", "application/json") + case "duplicate-token": + body(url.Values{"token": {raw, raw}}) + case "oversize": + body(url.Values{"token": {strings.Repeat("x", 65537)}}) + case "wrong-secret": + r.SetBasicAuth("test-client", "wrong-synthetic-secret") + case "missing-auth": + r.Header.Del("Authorization") + case "duplicate-auth": + r.Header.Add("Authorization", "Basic extra") + case "body-secret": + body(url.Values{"token": {raw}, "client_secret": {"unsupported"}}) + case "mismatched-client": + body(url.Values{"token": {raw}, "client_id": {"other"}}) + } + } + for _, revoke := range []bool{false, true} { + w := f.request(t, raw, "test-client", "synthetic-status-only", revoke, mutate) + if w.Code < 400 { + t.Fatal("malformed request accepted") + } + } + if f.introspect(t, raw)["active"] != true { + t.Fatal("refused request changed token") + } + }) + } +} + +type blockingStatusUsers struct { + *mockUserRepo + entered, release chan struct{} +} + +func (u *blockingStatusUsers) LookupUser(ctx context.Context, name string) (*domain.User, error) { + close(u.entered) + select { + case <-u.release: + return u.mockUserRepo.LookupUser(ctx, name) + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func TestTokenStatusRevocationDuringDirectoryLookupRefuses(t *testing.T) { + f := newStatusFixture(t, 8) + raw, _, _ := f.issue(t) + users := &blockingStatusUsers{mockUserRepo: f.users, entered: make(chan struct{}), release: make(chan struct{})} + f.status.Users = users + done := make(chan *httptest.ResponseRecorder, 1) + go func() { done <- f.request(t, raw, "test-client", "synthetic-status-only", false, nil) }() + select { + case <-users.entered: + case <-time.After(3 * time.Second): + t.Fatal("directory lookup not entered") + } + f.request(t, raw, "test-client", "synthetic-status-only", true, nil) + close(users.release) + select { + case w := <-done: + if strings.TrimSpace(w.Body.String()) != `{"active":false}` { + t.Fatal("delayed lookup resurrected revoked token") + } + case <-time.After(3 * time.Second): + t.Fatal("introspection did not finish") + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_transport_test.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_transport_test.go new file mode 100644 index 0000000..8bd0a16 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/token_status_transport_test.go @@ -0,0 +1,121 @@ +package oidc_test + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" +) + +func TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding(t *testing.T) { + f := newStatusFixture(t, 8) + f.token.ClientConfig["test-client"].ClientSecret = "synthetic + / colon: only" + mux := http.NewServeMux() + mux.Handle("/token", f.token) + mux.Handle("/userinfo", f.userinfo) + mux.HandleFunc("/introspect", f.status.Introspect) + mux.HandleFunc("/revoke", f.status.Revoke) + server := httptest.NewTLSServer(mux) + defer server.Close() + client := server.Client() + verifier, _ := makeVerifierAndChallenge() + code := seededSession(f.token.Sessions, verifier) + post := func(path string, form url.Values) (int, []byte) { + r, err := http.NewRequest(http.MethodPost, server.URL+path, strings.NewReader(form.Encode())) + if err != nil { + t.Fatal("request construction failed") + } + r.Header.Set("Content-Type", "application/x-www-form-urlencoded") + r.SetBasicAuth(url.QueryEscape("test-client"), url.QueryEscape(f.token.ClientConfig["test-client"].ClientSecret)) + resp, err := client.Do(r) + if err != nil { + t.Fatal("HTTPS fixture transport failed") + } + defer resp.Body.Close() + body, err := io.ReadAll(io.LimitReader(resp.Body, 65537)) + if err != nil || len(body) > 65536 { + t.Fatal("response read failed") + } + return resp.StatusCode, body + } + status, body := post("/token", url.Values{"grant_type": {"authorization_code"}, "client_id": {"test-client"}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {seededRedirectURI}}) + var tokens struct { + Access string `json:"access_token"` + } + if status != http.StatusOK || json.Unmarshal(body, &tokens) != nil || tokens.Access == "" { + t.Fatal("actual HTTPS PKCE exchange failed") + } + // A misleading type hint must not hide a supported access token. + form := url.Values{"token": {tokens.Access}, "token_type_hint": {"refresh_token"}} + status, body = post("/introspect", form) + var metadata map[string]interface{} + if status != http.StatusOK || json.Unmarshal(body, &metadata) != nil || metadata["active"] != true { + t.Fatal("actual HTTPS introspection failed") + } + if status, body = post("/revoke", form); status != http.StatusOK || len(body) != 0 { + t.Fatal("actual HTTPS revocation failed") + } + status, body = post("/introspect", form) + if status != http.StatusOK || strings.TrimSpace(string(body)) != `{"active":false}` { + t.Fatal("revocation did not affect HTTPS introspection") + } + r, _ := http.NewRequest(http.MethodGet, server.URL+"/userinfo", nil) + r.Header.Set("Authorization", "Bearer "+tokens.Access) + resp, err := client.Do(r) + if err != nil { + t.Fatal("HTTPS UserInfo failed") + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusUnauthorized || resp.Header.Get("Cache-Control") != "no-store" { + t.Fatal("real UserInfo accepted or cached revoked bearer") + } +} + +func TestTokenStatusRevocationDuringUserInfoLookupRefuses(t *testing.T) { + f := newStatusFixture(t, 8) + raw, _, _ := f.issue(t) + users := &blockingStatusUsers{mockUserRepo: f.users, entered: make(chan struct{}), release: make(chan struct{})} + f.userinfo.Users = users + done := make(chan int, 1) + go func() { done <- f.userinfoStatus(raw) }() + select { + case <-users.entered: + case <-time.After(3 * time.Second): + t.Fatal("UserInfo did not enter directory lookup") + } + f.request(t, raw, "test-client", "synthetic-status-only", true, nil) + close(users.release) + select { + case code := <-done: + if code != http.StatusUnauthorized { + t.Fatal("delayed UserInfo lookup resurrected revoked token") + } + case <-time.After(3 * time.Second): + t.Fatal("UserInfo did not finish") + } +} + +func TestTokenStatusLegacyGrantHasNoStatusClaim(t *testing.T) { + f := newStatusFixture(t, 8) + f.token.Tokens = nil + raw, id, code := f.issue(t) + if code != http.StatusOK { + t.Fatal("legacy issue failed") + } + for _, token := range []string{raw, id} { + if _, present := parseJWTPayload(t, token)["jti"]; present { + t.Fatal("disabled token contract changed") + } + } + if f.introspect(t, raw)["active"] != false || f.userinfoStatus(raw) != http.StatusUnauthorized { + t.Fatal("unregistered legacy token gained online authority") + } + f.userinfo.Tokens = nil + if f.userinfoStatus(raw) != http.StatusOK { + t.Fatal("legacy token no longer accepted in legacy mode") + } +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/userinfo.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/userinfo.go new file mode 100644 index 0000000..4e46956 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/src/internal/server/oidc/userinfo.go @@ -0,0 +1,280 @@ +package oidc + +import ( + "context" + "crypto" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "net/http" + "strings" + "time" + + "keycape/internal/domain" + "keycape/internal/server/telemetry" +) + +// UserinfoHandler implements GET /userinfo (OIDC Core §5.3). +// +// The endpoint validates the Bearer token, extracts the subject, looks up +// the user, and returns claims that are consistent with those in the ID token +// for the same scope set. +type UserinfoHandler struct { + Users domain.UserRepository + SigningKey *rsa.PublicKey // used to verify the incoming access token + Issuer string + Emitter telemetry.Emitter + Tokens *TokenStatusStore + ClientConfig map[string]*domain.Client +} + +// ServeHTTP handles GET /userinfo. +func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if h.Tokens != nil { + w.Header().Set("Cache-Control", "no-store") + w.Header().Set("Pragma", "no-cache") + } + + // 1. Extract Bearer token. + tokenStr, ok := bearerToken(r) + if !ok { + http.Error(w, `{"error":"missing_token","description":"Authorization: Bearer required"}`, http.StatusUnauthorized) + return + } + + // 2. Validate token (algorithm, signature, expiry, issuer, purpose) and + // extract claims. + claims, err := validateAccessToken(tokenStr, h.SigningKey, h.Issuer) + if err != nil { + http.Error(w, `{"error":"invalid_token","description":"token validation failed"}`, http.StatusUnauthorized) + return + } + if h.Tokens != nil { + if _, active := h.Tokens.active(tokenStr, h.ClientConfig, time.Now()); !active { + http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized) + return + } + } + + // 3. Extract sub claim (which is the username in our model). + sub, _ := claims["sub"].(string) + if sub == "" { + http.Error(w, `{"error":"invalid_token","description":"missing sub claim"}`, http.StatusUnauthorized) + return + } + + // 4. Resolve the canonical subject. Human tokens use the stable directory + // ID (the LDAP DN) as sub, while LookupUser accepts an LDAP uid. Prefer the + // scoped username hint but require it to resolve back to the signed subject; + // fall back to an ID scan for openid-only tokens and renamed users. + user, err := lookupUserBySubject(ctx, h.Users, claims, sub) + if err != nil { + // User referenced in token but not found → treat as invalid token. + http.Error(w, `{"error":"invalid_token","description":"subject not found"}`, http.StatusUnauthorized) + return + } + if isSuspended(user) { + http.Error(w, `{"error":"invalid_token","description":"subject is suspended"}`, http.StatusUnauthorized) + return + } + if h.Tokens != nil { + if _, active := h.Tokens.active(tokenStr, h.ClientConfig, time.Now()); !user.Enabled || !active { + http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized) + return + } + } + + // 5. Build response claims filtered by the scopes embedded in the token. + scopeStr, _ := claims["scope"].(string) + scopeSet := parseScopeSet(scopeStr) + + resp := map[string]interface{}{ + "sub": sub, + } + + if scopeSet["profile"] { + resp["preferred_username"] = user.Username + resp["name"] = user.DisplayName + } + if scopeSet["email"] { + resp["email"] = user.Email + } + if scopeSet["groups"] { + resp["groups"] = user.Groups + } + + // 6. Emit telemetry. + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: time.Now(), + EventType: telemetry.EventAuthSuccess, + Endpoint: "/userinfo", + Result: "success", + }) + + // 7. Write JSON response. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} + +func lookupUserBySubject( + ctx context.Context, + users domain.UserRepository, + claims map[string]interface{}, + sub string, +) (*domain.User, error) { + if username, _ := claims["preferred_username"].(string); username != "" { + if user, err := users.LookupUser(ctx, username); err == nil && user.ID == sub { + return user, nil + } + } + + // Retain compatibility with older signed tokens whose subject was the uid. + if user, err := users.LookupUser(ctx, sub); err == nil && + (user.ID == sub || user.Username == sub) { + return user, nil + } + + all, err := users.ListUsers(ctx) + if err != nil { + return nil, err + } + for i := range all { + if all[i].ID == sub { + return &all[i], nil + } + } + return nil, domain.ErrUserNotFound +} + +// --------------------------------------------------------------------------- +// JWT validation (stdlib only — no external JWT library) +// --------------------------------------------------------------------------- + +// validateAccessToken parses and validates a KeyCape-issued access token. +// +// Beyond signature and expiry it enforces the bindings the caller CLI already +// requires (KEY-WP-0016-T03): the JOSE header algorithm must be exactly RS256, +// so an "alg":"none" or HMAC-shaped token can never bypass the RSA check; the +// issuer claim must equal this issuer, so a correctly-signed token from another +// deployment is refused; and the token must be an access token. +// +// Purpose is decided on the presence of the `scope` claim, which the token +// endpoint sets on access tokens and never on ID tokens. That keeps the check +// verification-side: it does not add a claim to the issued token contract, which +// consumers pin exactly. +func validateAccessToken(tokenStr string, pubKey *rsa.PublicKey, issuer string) (map[string]interface{}, error) { + parts := strings.Split(tokenStr, ".") + if len(parts) != 3 { + return nil, errors.New("malformed JWT: expected 3 parts") + } + + // Verify the JOSE header algorithm before trusting the signature check. + headerJSON, err := base64.RawURLEncoding.DecodeString(parts[0]) + if err != nil { + return nil, errors.New("malformed JWT: invalid header encoding") + } + var header struct { + Alg string `json:"alg"` + } + if err := json.Unmarshal(headerJSON, &header); err != nil { + return nil, errors.New("malformed JWT: header is not valid JSON") + } + if header.Alg != "RS256" { + return nil, errors.New("unsupported JWT algorithm") + } + + // Verify signature over header.payload. + signingInput := parts[0] + "." + parts[1] + digest := sha256.Sum256([]byte(signingInput)) + + sigBytes, decodeErr := base64.RawURLEncoding.DecodeString(parts[2]) + if decodeErr != nil { + return nil, errors.New("malformed JWT: invalid signature encoding") + } + + if err := rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, digest[:], sigBytes); err != nil { + return nil, errors.New("JWT signature verification failed") + } + + // Decode payload. + payloadJSON, payloadErr := base64.RawURLEncoding.DecodeString(parts[1]) + if payloadErr != nil { + return nil, errors.New("malformed JWT: invalid payload encoding") + } + + var claims map[string]interface{} + if err := json.Unmarshal(payloadJSON, &claims); err != nil { + return nil, errors.New("malformed JWT: payload is not valid JSON") + } + + // Check exp claim. + exp, ok := claims["exp"].(float64) + if !ok { + return nil, errors.New("JWT missing exp claim") + } + if time.Now().Unix() > int64(exp) { + return nil, errors.New("JWT has expired") + } + + // Require this issuer. An empty configured issuer would make the check + // vacuous, so treat that as a misconfiguration rather than skipping it. + if issuer == "" { + return nil, errors.New("issuer is not configured") + } + if tokenIssuer, _ := claims["iss"].(string); tokenIssuer != issuer { + return nil, errors.New("JWT issuer mismatch") + } + + // Require an access token. ID tokens carry no scope claim. + if _, ok := claims["scope"]; !ok { + return nil, errors.New("JWT is not an access token") + } + + return claims, nil +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +// bearerToken extracts the token from the Authorization header. +// Returns ("", false) when the header is missing or not a Bearer token. +func bearerToken(r *http.Request) (string, bool) { + hdr := r.Header.Get("Authorization") + if hdr == "" { + return "", false + } + const prefix = "Bearer " + if !strings.HasPrefix(hdr, prefix) { + return "", false + } + tok := strings.TrimSpace(hdr[len(prefix):]) + if tok == "" { + return "", false + } + return tok, true +} + +// parseScopeSet converts a space-separated scope string to a set. +func parseScopeSet(scope string) map[string]bool { + set := make(map[string]bool) + for _, s := range strings.Fields(scope) { + set[s] = true + } + return set +} + +// --------------------------------------------------------------------------- +// BuildJWT — exported for test helpers +// --------------------------------------------------------------------------- + +// BuildJWT is an exported wrapper around the internal buildJWT function so +// that tests in the oidc_test package can construct valid tokens for the +// UserinfoHandler without importing an external JWT library. +func BuildJWT(claims map[string]interface{}, kid string, key *rsa.PrivateKey) (string, error) { + return buildJWT(claims, kid, key) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/ADHOC-2026-09-27.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/ADHOC-2026-09-27.md new file mode 100644 index 0000000..a206833 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/ADHOC-2026-09-27.md @@ -0,0 +1,101 @@ +--- +id: ADHOC-2026-09-27 +type: workplan +title: "Fresh MFA no longer backdated to a stale kc_login cookie (INFD-IN-0005)" +domain: infotech +repo: key-cape +status: finished +owner: claude +created: "2026-09-27" +updated: "2026-09-27" +state_hub_workstream_id: "5217dd06-73cb-50f8-ba64-528e421b29de" +--- + +## Stop completeAuthorization from reusing a stale prior session's IssuedAt after real MFA + +```task +id: ADHOC-2026-09-27-T01 +status: done +priority: high +state_hub_task_id: "ce98e4af-cfaa-5620-8f21-15ade18ee9e8" +``` + +`informed-decision` intake `INFD-IN-0005` (open, `origin_ref: SECRETS-WP-0010`): +platform-root's approval of `infd-20260927-b01` was refused by flex-auth's +`compact_sitting` policy with `policy_denied`, despite platform-root having just +completed a fresh key-cape MFA sign-in. flex-auth's 900-second review-freshness +check reads `assurance.at`, which key-cape derives from the PKCE session's +`AuthTime`. + +Root cause in `src/internal/server/oidc/authorize.go`, +`AuthorizeHandler.completeAuthorization`: whenever a `kc_login` cookie already +existed for the same username, its `IssuedAt` was unconditionally reused as +`authTime` — even on the two call sites that had just called +`MFA.ValidateMFAToken` successfully in this exact request (the callback's +`mfaToken != ""` branch and `serveMFASubmission`). A user who had an older +browser session and then stepped up with a fresh MFA challenge got the *old* +session's timestamp stamped onto the *new*, just-proven authentication — +understating freshness for every downstream binding-grade check, exactly as +`INFD-IN-0005` describes ("completeAuthorization currently copies a prior +timestamp... only after actual reauthentication"). + +The reuse itself is correct when MFA was **not** just performed in this +request (`decision.MFAVerified` carried forward from an existing AAL2 session, +via `domain.DecideAssurance`) — reporting mint time there would overstate +freshness (`TestAssuranceReportsAuthenticationTimeNotMintTime`). The two cases +were conflated under a single `mfaVerified` bool. + +Fix: added a `freshMFA bool` parameter to `completeAuthorization`, `true` only +at the two call sites that just validated an MFA token in this request, `false` +at the carried-forward-assurance call site. `authTime` now falls back to the +prior session's `IssuedAt` only when `!freshMFA`. + +Added `TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie`, +which seeds a 4-hour-old `kc_login` cookie for the same user, then submits a +valid MFA token and asserts the resulting `AuthTime` is within a minute of now. +Confirmed it fails against the pre-fix code (`AuthTime` ~4h old) and passes +after. Full suite (`go test ./...`) green, no other test needed adjustment — +the carried-forward-assurance case (`TestPolicy_...`, `TestLogout_...`, +`assurance_time_test.go`) is unaffected since none of it exercises a fresh MFA +submission with a pre-existing cookie for the same user. + +Does not touch flex-auth (its 900s window is correct; it was consuming a bad +timestamp) or informed-decision (correctly refuses to interpret the PDP's +denial). `INFD-IN-0005` can close once informed-decision confirms platform-root +can approve after a fresh sign-in against this build. + +## Discovery/JWKS fetch was missing the split-horizon origin headers + +```task +id: ADHOC-2026-09-27-T02 +status: done +priority: high +state_hub_task_id: "7fa0bfc7-0772-57c7-8d6f-641615cab82a" +``` + +Rolling out T01 (image digest `sha256:248e449a...`) broke every KeyCape login, +including unrelated clients (`user-engine-portal`), with `auth_failure` / +`provider_metadata_unavailable` at `/api/oidc/token` on the KeyCape side and +Authelia logging `invalid X-Forwarded-Proto header value 'http'` on +`GET /.well-known/openid-configuration` from KeyCape's pod IP. + +Root cause, unrelated to T01's change: `AutheliaAdapter.setPublicOriginHeaders` +(the NK-WP-0041-T02/path-B fix, key-cape@3b0446e) annotates only the token +exchange (`newTokenRequest`) with `X-Forwarded-Proto`/`X-Forwarded-Host` so +Authelia can derive the public issuer for a split-horizon (in-cluster HTTP, +public HTTPS) deployment. `idTokenVerifier.get` in `verify.go` — used for the +provider-metadata and JWKS fetches that verify every upstream ID token — never +got the same treatment; it built a bare request with only an `Accept` header. +Authelia 4.38 fails closed on the resulting unmarked in-cluster request. This +gap predates today's session (present since 3b0446e); it surfaced now because +this was the first login attempted since that image went live. + +Fix: extracted `setPublicOriginHeaders` from an `AutheliaAdapter` method to a +package-level function taking the target public base URL explicitly, and gave +`idTokenVerifier` its own resolved `publicBaseURL` (same +`BrowserBaseURL`-else-`BaseURL` fallback as the adapter) so `get()` applies +the same headers. Added `TestDiscoveryFetch_CarriesPublicOriginHeaders` +(confirmed it fails without the fix, passes with it). Full suite green. + +New image `key-cape@` still needs a rollout to `sso-mfa/k8s/keycape/deployment.yaml` +before platform-root can sign in again. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/KEY-WP-0042-online-access-token-status.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/KEY-WP-0042-online-access-token-status.md new file mode 100644 index 0000000..66f9094 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/postimages/workplans/KEY-WP-0042-online-access-token-status.md @@ -0,0 +1,84 @@ +--- +id: KEY-WP-0042 +type: workplan +title: "Provide issuer-owned current access-token status and scoped revocation" +domain: infotech +repo: key-cape +status: proposed +owner: codex +topic_slug: custodian +created: "2026-10-18" +updated: "2026-10-18" +flavor: extension +depends_on: [] +state_hub_workstream_id: "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38" +--- + +CUST-WP-0080's current-operation/inverse requirement exposed a concrete missing +issuer facility. KEY-WP-0025-T03 truthfully documents the expiry-only limit; +KEY-WP-0038 owns Forgejo's existing client, not a token-liveness API. This is an +explicit proposed scope extension in the existing issuer, not a silent change +to that completed limit record. No new service or duplicate session authority. +Consumer binding/authorization and native case acceptance stay in +NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09; they are not copied here. + +## Review the bounded issuer/profile extension + +```task +id: KEY-WP-0042-T01 +status: progress +priority: high +state_hub_task_id: "d8af8e77-f6e5-5d0d-8b44-794af3dee5cf" +``` + +The reviewable contract is `docs/online-token-status.md`: opt-in RFC 7662/7009 +access-token status; registered owning-client authentication; private hash-only +bounded state; unique access-token jti; no ID-token change; singleton store-loss +refusal; disabled legacy behavior explicit. Source preparation is authorized by +the full Custodian implementation goal. Independent issuer/profile/consumer +assent remains required before admission; no such assent is inferred here. + +## Implement and verify the actual issuer paths + +```task +id: KEY-WP-0042-T02 +status: done +priority: high +state_hub_task_id: "2cade51f-d25a-5719-96b4-b57d50c1b97d" +``` + +Wire the same issuer store to both actual grant types, UserInfo, introspection, +revocation, configuration validation and implemented discovery. Verify distinct +grants, exact expiry, replay/tombstones, capacity, malformed/unauthenticated and +cross-client refusals, current registration and human disposition, revocation +during directory lookup, process-store loss and absence of secret output. Keep +the complete existing source suite, disabled profile and build passing. + +## Admit and accept the issuer lifecycle at native scope + +```task +id: KEY-WP-0042-T03 +status: todo +priority: high +state_hub_task_id: "a5539bb0-2501-5774-a7d8-acddfb0938ab" +``` + +After independent contract review, admit exact issuer image/config/profile, +HTTPS ingress/client custody and measured capacity/headroom. Exercise real +human PKCE and service tokens, actual endpoint authentication, revoked-token +UserInfo refusal, expiry, unavailable dependency, restart/store loss and fresh +reauthentication. Preserve old offline-token semantics and document every +consumer's selected mode. Review disabling/rollback explicitly; never silently +fall back to offline acceptance for online-profile consumers. Provide owner +receipts to existing NK48/Apps consumer tasks and Audit source admission. + +2026-10-18 source verification: all 659 test/subtest results in the complete +race suite pass, with six preexisting native/external skips. The standalone +binary passes 26 isolated process checks, including actual main/router/config, +service issuance, capacity, revocation, process restart, fresh issuance and +disabled-profile behavior; fixture diagnostics omit synthetic private inputs. +The HTTPS human PKCE flow and revocation during directory lookups also pass. +An isolated control omitting the revoked-state predicate fails seven intended +checks. This closes source task T02 only. T01 independent issuer/profile review, +T03 native admission and all existing consumer operation/effect bindings remain +open. Evidence is retained by CUST-WP-0080 under keycape-token-status-r1. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal-manifest.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal-manifest.json new file mode 100644 index 0000000..e6b4d0a --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal-manifest.json @@ -0,0 +1,102 @@ +{ + "owner": "key-cape", + "base_revision": "git:d803e9282a47b49daf930fb905312fa5104c0ace", + "files": [ + { + "path": "SCOPE.md", + "before_sha256": "sha256:20fbadf3e33339bca4b541cdfa33a0de00095c968d51006dc4d0a46735cc834f", + "sha256": "sha256:a80e80ba2e5279ba50232e6b3f57a5bdd14866e584d2d055dfb09e98a4c24ce0" + }, + { + "path": "docs/online-token-status.md", + "before_sha256": null, + "sha256": "sha256:f5e41b47500b15c80b2da6cbe37a9e64bcbde1c072750934cf74464a19488925" + }, + { + "path": "docs/operations.md", + "before_sha256": "sha256:e68ec32073b7731214308ccd2fceb522d934b00f1d76e6d438e2870030497cfc", + "sha256": "sha256:3659362e3c7f8720e52cc8dc7c67a6295ac26462f383c68c06c120fac6e6e472" + }, + { + "path": "src/cmd/keycape/main.go", + "before_sha256": "sha256:ff1ee4f5d2eb86a1e4184374d5244e24704d9d5fd780e173062af07112344afd", + "sha256": "sha256:2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d" + }, + { + "path": "src/internal/config/config.go", + "before_sha256": "sha256:afd465acd9b6879493b888acc6fc54b9a71e032892701a81f1072986d2c30ac7", + "sha256": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683" + }, + { + "path": "src/internal/config/token_status_test.go", + "before_sha256": null, + "sha256": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5" + }, + { + "path": "src/internal/config/validate.go", + "before_sha256": "sha256:8189ee82e7df74a2a4a1f53e213b1be0a5c737fd76d8f283f7ec9f8ed38c3a65", + "sha256": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405" + }, + { + "path": "src/internal/server/oidc/discovery.go", + "before_sha256": "sha256:5ff9ddd9e2f4304b562e116100ca31fe7eabf8315d7d73998d3e6c5bea53956d", + "sha256": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5" + }, + { + "path": "src/internal/server/oidc/token.go", + "before_sha256": "sha256:8422cc9dbd39c88dfe835b43f6262cb120bc0d2e5b08898a151167b7555368d3", + "sha256": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f" + }, + { + "path": "src/internal/server/oidc/token_status.go", + "before_sha256": null, + "sha256": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3" + }, + { + "path": "src/internal/server/oidc/token_status_discovery_test.go", + "before_sha256": null, + "sha256": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031" + }, + { + "path": "src/internal/server/oidc/token_status_http.go", + "before_sha256": null, + "sha256": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97" + }, + { + "path": "src/internal/server/oidc/token_status_internal_test.go", + "before_sha256": null, + "sha256": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53" + }, + { + "path": "src/internal/server/oidc/token_status_test.go", + "before_sha256": null, + "sha256": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90" + }, + { + "path": "src/internal/server/oidc/token_status_transport_test.go", + "before_sha256": null, + "sha256": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347" + }, + { + "path": "src/internal/server/oidc/userinfo.go", + "before_sha256": "sha256:bbd5fa9ebd0726371c90d6938536c1ce5fcfacbbe853a80833e3587fc2115a0b", + "sha256": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f" + }, + { + "path": "workplans/KEY-WP-0042-online-access-token-status.md", + "before_sha256": null, + "sha256": "sha256:a8901d5aed2cc2e653750852d2431e9a03c3eff766f09afac2f7a4301a06b4f6" + } + ], + "patch_sha256": "sha256:926f36443c36c6568ba4fb5f7db26697bcab01f8ce0beaa57ba227329f156a13", + "replay": { + "fuzz": 0, + "all_postimages_exact": true, + "all_unrelated_archive_files_unchanged": true, + "stdout": "patching file SCOPE.md\npatching file docs/online-token-status.md\npatching file docs/operations.md\npatching file src/cmd/keycape/main.go\npatching file src/internal/config/config.go\npatching file src/internal/config/token_status_test.go\npatching file src/internal/config/validate.go\npatching file src/internal/server/oidc/discovery.go\npatching file src/internal/server/oidc/token.go\npatching file src/internal/server/oidc/token_status.go\npatching file src/internal/server/oidc/token_status_discovery_test.go\npatching file src/internal/server/oidc/token_status_http.go\npatching file src/internal/server/oidc/token_status_internal_test.go\npatching file src/internal/server/oidc/token_status_test.go\npatching file src/internal/server/oidc/token_status_transport_test.go\npatching file src/internal/server/oidc/userinfo.go\npatching file workplans/KEY-WP-0042-online-access-token-status.md\n", + "stderr": "" + }, + "canonical_owner_modified": false, + "independent_owner_assent": false, + "native_admission": false +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal.patch b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal.patch new file mode 100644 index 0000000..d2691b0 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preceding-proposal.patch @@ -0,0 +1,1454 @@ +diff --git a/SCOPE.md b/SCOPE.md +--- a/SCOPE.md ++++ b/SCOPE.md +@@ -42,8 +42,10 @@ + + - Client registrations, service secrets and the signing key are loaded at startup. + Login, authorization and handoff state are process-local. There is no shared +- session store, general refresh-token flow, token introspection/revocation API, +- or automatic signing-key/client-secret rotation service. Logout clears the ++ session store, general refresh-token flow, or automatic signing-key/client-secret ++ rotation service. The default profile remains expiry-only. Proposed KEY-WP-0042 ++ adds an explicitly enabled, singleton, hash-only online access-token status ++ profile; see `docs/online-token-status.md` for its scope and admission limits. Logout clears the + local KeyCape session, not every upstream or downstream session/token. + - The optional tenant-engine `tenant_roles` adapter is wired through a + `tenantEngine` config block and is off unless `baseURL` is set (KEY-WP-0024). +diff --git a/docs/online-token-status.md b/docs/online-token-status.md +--- /dev/null ++++ b/docs/online-token-status.md +@@ -0,0 +1,119 @@ ++# Optional online access-token status ++ ++Source proposal under KEY-WP-0042, driven by CUST-WP-0080 and existing ++NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09. Independent issuer/consumer ++contract review and native admission remain required. Existing deployments are ++unchanged. This extends the bounded issuer; it does not introduce a broker, ++approval store, PDP, or a second application-local token registry. ++ ++The legacy profile authenticates self-contained JWTs until expiry. That does ++not establish that an operation's initiating token is still unrevoked. The ++optional profile adds issuer-owned current access-token status using ++[RFC 7662](https://www.rfc-editor.org/rfc/rfc7662.html) introspection and ++[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009.html) revocation. It supports ++access tokens only, without refresh tokens or token exchange. ++ ++## Enablement and lifecycle ++ ++Owner-admitted configuration opts in explicitly: ++ ++```yaml ++tokenStatus: ++ enabled: true ++ maxEntries: 10000 ++``` ++ ++The issuer must use HTTPS at its existing trusted ingress. The server's internal ++HTTP listener and reverse-proxy topology are unchanged; direct HTTP exposure is ++not admitted by an HTTPS issuer string. Omitted configuration keeps the existing ++expiry-only token/UserInfo contract and advertises no new endpoints. Enabling ++status registers `/introspect` and `/revoke`, advertises their implemented client ++authentication methods, and connects the same store to issuance and UserInfo. ++ ++Human PKCE and service grants add a cryptographically random `jti` to each access ++token. ID-token claims and existing assurance/tenant provenance remain unchanged. ++Distinct grants in the same second have distinct bearer tokens: a new grant can ++never remint a revoked token. A successful response is emitted only after the ++token hash, expiry and current registration commitment have been recorded. No ++bearer, client secret or identity/group snapshot is retained in the status store. ++ ++The store is bounded to the configured count (default 10000, maximum 1000000). ++Live records and revocation tombstones are never evicted to admit another grant; ++capacity exhaustion refuses issuance with a fixed 503. Expired entries are ++collected when admitting a new token. Capacity and per-client ingress rate limits ++need workload-representative admission: this is not a throughput/availability ++claim or protection against an authorized client filling the bound. ++ ++This profile uses KeyCape's existing **single-instance topology**. Status is ++process-local. Restart, deployment, rollback or store loss makes every earlier ++token inactive at online endpoints, even if its JWT signature/expiry still ++validates offline. Consumers must reauthenticate; there is no recovery replay ++or token-state import that can resurrect revoked authority. Multiple replicas ++are unsupported. Native admission must include this interruption and the ++measured reauthentication/recovery procedure. A persisted/shared store is not ++introduced here. ++ ++## Introspection and revocation ++ ++Both endpoints accept POST form bodies only, bound to 64 KiB; token values are ++bound to 32 KiB. Query parameters, duplicate form or authentication fields, ++unsupported content types and malformed authentication refuse without echoing ++input. Responses use `Cache-Control: no-store` and `Pragma: no-cache`. ++ ++Introspection requires the existing registered confidential client's ++`client_secret_basic` authentication. Only that client's own issued tokens can ++be inspected. A separate resource audience is returned as `aud`, not confused ++with the issuing `client_id`. Cross-client resource-server delegation needs a ++separately reviewed authorization contract and is not implicitly granted by a ++matching audience string. Public clients cannot enumerate token status. ++ ++An active response means the exact access token was issued in this process, ++has not expired or been revoked, still matches its current complete registration, ++and passes current issuer/signature/purpose validation. For humans, directory ++lookup must succeed and the current account must be enabled and not suspended. ++Status is checked again after directory lookup. Service subjects remain bound ++to their current registration. Active output contains only standard access-token ++metadata; inactive output contains only `active: false`. Unknown tokens, ID ++tokens, altered tokens, another client's tokens and expired/revoked tokens all ++return that same inactive response to an authenticated introspector. ++ ++Revocation authenticates confidential clients using the same method. A registered ++public client may revoke its own token using `client_id` plus possession of that ++bearer; public-client introspection remains unavailable. Revocation only affects ++the exact token issued to that client. Unknown, other-client, expired and already ++revoked tokens all produce the same empty 200. No token existence is disclosed. ++Token-type hints do not bypass the actual access-token lookup. Removal or changes ++to a registration invalidate online status; the commitment explicitly includes ++the secret digest and internal token lifetime omitted from public Client JSON. ++ ++When enabled, actual UserInfo consults the same store before lookup and again ++after it. Revocation therefore affects that existing endpoint immediately on its ++next check. Ordinary login/logout and upstream Authelia sessions are distinct: ++neither browser logout nor deleting a local cookie is represented as global ++JWT revocation. ++ ++## Consumer contract and remaining acceptance ++ ++Online status establishes current access-token status, not permission ++for an application resource or privileged effect. Consumers still independently ++validate the actual signed token, admitted issuer/key/audience/type, subject, ++tenant, principal type, assurance and freshness. The authenticated introspection ++response must refer to that exact token/client. Obtain a new current response ++before each protected operation; unavailable, malformed, inactive or ambiguous ++responses refuse. A cached positive is not a current-session check. ++ ++Access Engine retains resource/action decisions, Approval Engine exact one-shot ++approval, Warden admission and owner adapters execution. Bind their existing ++descriptor, case, transaction and exact effect before dispatch. A separate new ++operation/inverse token must be acquired and revoked on every terminal path; ++renewed authentication cannot revive a consumed approval or replay a mutation. ++This issuer capability alone implements none of those consumer bindings. ++ ++Native completion still requires independent issuer/profile review, exact ++image/config/trust admission, bounded retained-token custody, real human/service ++issuance, private transport, current-token/revocation integration in each owner, ++all exit-path cleanup, restart/expiry/unavailable/refusal checks, ongoing ++HTTP/API/Git/SSH authorization and audit evidence. Offline consumers which never ++consult the endpoint retain their existing JWT validity; no estate-wide immediate ++revocation claim follows from source implementation. Disabling the feature is ++not an acceptable fallback for a consumer admitted under the online profile. +diff --git a/docs/operations.md b/docs/operations.md +--- a/docs/operations.md ++++ b/docs/operations.md +@@ -20,7 +20,8 @@ + Consequences to plan for: + + - **A restart drops in-flight logins.** Anyone mid-login gets an error and must +- start again. Issued tokens are unaffected — they are self-contained JWTs and ++ start again. In the default profile issued tokens are unaffected — they are ++ self-contained JWTs and + stay valid until they expire. + - **Rolling deployments are single-instance rollovers**, so expect a brief window + where new logins fail. Draining (below) protects requests already in flight, not +@@ -89,7 +90,8 @@ + a same-`kid` swap as a breaking change for anyone caching aggressively. + + **Removing a client registration does not revoke tokens already issued to it.** +-There is no introspection or revocation endpoint, so an issued token stays valid ++In the default expiry-only profile there is no introspection or revocation ++endpoint, so an issued token stays valid + until it expires — 15 minutes by default, or the client's `tokenLifetime`. To cut + off a compromised client, remove the registration *and* wait out the lifetime, or + rotate the signing key if you cannot. +@@ -281,3 +283,12 @@ + `--operation remove` plus a restart is the rollback. A redirect URI that is not http(s) + skips the page. Every `auth_success` log line now carries `client_id`, `uid` and + `assurance`, and no token, code, email or one-time password. ++ ++## Proposed online token-status profile ++ ++KEY-WP-0042 adds an explicitly enabled singleton profile. It makes prior tokens ++inactive at online endpoints after restart and keeps revoked-token tombstones ++until expiry. It does not revoke offline consumers or upstream/browser sessions. ++Read [the complete lifecycle and admission contract](online-token-status.md) ++before proposing activation; no image/config rollout or consumer admission is ++established by source tests. Existing deployed configuration remains unchanged. +diff --git a/src/cmd/keycape/main.go b/src/cmd/keycape/main.go +--- a/src/cmd/keycape/main.go ++++ b/src/cmd/keycape/main.go +@@ -149,6 +149,24 @@ + // 12. Register HTTP handlers. + // ----------------------------------------------------------------- + mux := http.NewServeMux() ++ var tokenStatus *oidc.TokenStatusStore ++ var introspectionEndpoint, revocationEndpoint string ++ if cfg.TokenStatus.Enabled { ++ limit := cfg.TokenStatus.MaxEntries ++ if limit == 0 { ++ limit = 10000 ++ } ++ var err error ++ tokenStatus, err = oidc.NewTokenStatusStore(limit) ++ if err != nil { ++ log.Error().Msg("invalid token-status configuration") ++ os.Exit(1) ++ } ++ status := &oidc.TokenStatusHandler{Tokens: tokenStatus, ClientConfig: clients, Users: lldapAdapter, SigningKey: &privateKey.PublicKey, Issuer: issuer} ++ mux.HandleFunc("/introspect", status.Introspect) ++ mux.HandleFunc("/revoke", status.Revoke) ++ introspectionEndpoint, revocationEndpoint = issuer+"/introspect", issuer+"/revoke" ++ } + + // Discovery. + mux.Handle("/.well-known/openid-configuration", oidc.NewDiscoveryHandler(oidc.DiscoveryConfig{ +@@ -158,6 +176,8 @@ + JWKSUri: issuer + "/jwks", + UserinfoEndpoint: issuer + "/userinfo", + EndSessionEndpoint: issuer + "/logout", ++ IntrospectionEndpoint: introspectionEndpoint, ++ RevocationEndpoint: revocationEndpoint, + Clients: clients, + })) + +@@ -218,15 +238,18 @@ + // fails open, so a configured-but-unreachable cache omits the claim + // rather than failing issuance. + TenantEngine: buildTenantEngine(cfg.TenantEngine), ++ Tokens: tokenStatus, + } + mux.Handle("/token", enforcement.Middleware(tokenHandler)) + + // Userinfo handler. + mux.Handle("/userinfo", &oidc.UserinfoHandler{ +- Users: lldapAdapter, +- SigningKey: &privateKey.PublicKey, +- Issuer: issuer, +- Emitter: emitter, ++ Users: lldapAdapter, ++ SigningKey: &privateKey.PublicKey, ++ Issuer: issuer, ++ Emitter: emitter, ++ Tokens: tokenStatus, ++ ClientConfig: clients, + }) + + // Healthz. +diff --git a/src/internal/config/config.go b/src/internal/config/config.go +--- a/src/internal/config/config.go ++++ b/src/internal/config/config.go +@@ -30,6 +30,14 @@ + Clients []ClientConfig `yaml:"clients"` + Environment string `yaml:"environment"` + TenantEngine TenantEngineConfig `yaml:"tenantEngine,omitempty"` ++ TokenStatus TokenStatusConfig `yaml:"tokenStatus,omitempty"` ++} ++ ++// TokenStatusConfig opts a singleton issuer into online access-token status. ++// Existing deployment configuration remains expiry-only until owner admission. ++type TokenStatusConfig struct { ++ Enabled bool `yaml:"enabled"` ++ MaxEntries int `yaml:"maxEntries,omitempty"` + } + + // TenantEngineConfig configures the optional tenant_roles cache claim. +diff --git a/src/internal/config/token_status_test.go b/src/internal/config/token_status_test.go +--- /dev/null ++++ b/src/internal/config/token_status_test.go +@@ -0,0 +1,41 @@ ++package config_test ++ ++import ( ++ "strings" ++ "testing" ++ ++ "keycape/internal/config" ++) ++ ++func TestTokenStatusConfigIsExplicitBoundedAndHTTPS(t *testing.T) { ++ for _, kind := range []string{"legacy", "enabled-default", "enabled-bounded", "http", "userinfo-url", "query", "too-large", "negative", "disabled-capacity"} { ++ t.Run(kind, func(t *testing.T) { ++ cfg := validConfig("unused-public-test-key-path") ++ cfg.TokenStatus.Enabled = kind != "legacy" && kind != "disabled-capacity" ++ switch kind { ++ case "enabled-bounded": ++ cfg.TokenStatus.MaxEntries = 128 ++ case "http": ++ cfg.Issuer = "http://auth.example.com" ++ case "userinfo-url": ++ cfg.Issuer = "https://name@auth.example.com" ++ case "query": ++ cfg.Issuer = "https://auth.example.com?query=present" ++ case "too-large": ++ cfg.TokenStatus.MaxEntries = 1000001 ++ case "negative": ++ cfg.TokenStatus.MaxEntries = -1 ++ case "disabled-capacity": ++ cfg.TokenStatus.MaxEntries = 128 ++ } ++ var statusError bool ++ for _, err := range config.ValidateConfig(cfg) { ++ statusError = statusError || strings.HasPrefix(err, "tokenStatus") ++ } ++ expected := kind != "legacy" && kind != "enabled-default" && kind != "enabled-bounded" ++ if statusError != expected { ++ t.Fatal("token-status configuration acceptance mismatch") ++ } ++ }) ++ } ++} +diff --git a/src/internal/config/validate.go b/src/internal/config/validate.go +--- a/src/internal/config/validate.go ++++ b/src/internal/config/validate.go +@@ -12,6 +12,17 @@ + // Called at startup — the server must exit 1 if any errors are returned. + func ValidateConfig(cfg *Config) []string { + var errs []string ++ if cfg.TokenStatus.Enabled { ++ u, err := url.Parse(cfg.Issuer) ++ if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" { ++ errs = append(errs, "tokenStatus: requires an HTTPS issuer without credentials, query or fragment") ++ } ++ if cfg.TokenStatus.MaxEntries < 0 || cfg.TokenStatus.MaxEntries > 1000000 { ++ errs = append(errs, "tokenStatus.maxEntries: must be between 1 and 1000000, or omitted for 10000") ++ } ++ } else if cfg.TokenStatus.MaxEntries != 0 { ++ errs = append(errs, "tokenStatus.maxEntries: set while token status is disabled") ++ } + for name, raw := range map[string]string{"accountPortalURL": cfg.AccountPortalURL, "browserLogoutURL": cfg.BrowserLogoutURL} { + if raw == "" { + continue +diff --git a/src/internal/server/oidc/discovery.go b/src/internal/server/oidc/discovery.go +--- a/src/internal/server/oidc/discovery.go ++++ b/src/internal/server/oidc/discovery.go +@@ -20,6 +20,8 @@ + JWKSUri string + UserinfoEndpoint string // optional, empty = not advertised + EndSessionEndpoint string // optional, empty = not advertised ++ IntrospectionEndpoint string // opt-in online access-token status ++ RevocationEndpoint string // opt-in access-token revocation + // Clients supplies the registered clients so scopes_supported reflects the + // scopes this deployment can actually grant, rather than a fixed list that + // omits every configured resource scope (KEY-WP-0017-T03). Nil or empty +@@ -31,22 +33,26 @@ + // Fields are ordered to match common OIDC implementations for readability. + // registration_endpoint is intentionally absent — no dynamic client registration. + type discoveryDocument struct { +- Issuer string `json:"issuer"` +- AuthorizationEndpoint string `json:"authorization_endpoint"` +- TokenEndpoint string `json:"token_endpoint"` +- JWKSUri string `json:"jwks_uri"` +- UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` +- EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` +- ResponseTypesSupported []string `json:"response_types_supported"` +- GrantTypesSupported []string `json:"grant_types_supported"` +- CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` +- IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` +- ScopesSupported []string `json:"scopes_supported"` +- TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` +- ClaimsSupported []string `json:"claims_supported"` +- SubjectTypesSupported []string `json:"subject_types_supported"` +- RequestParameterSupported bool `json:"request_parameter_supported"` +- ClaimsParameterSupported bool `json:"claims_parameter_supported"` ++ Issuer string `json:"issuer"` ++ AuthorizationEndpoint string `json:"authorization_endpoint"` ++ TokenEndpoint string `json:"token_endpoint"` ++ JWKSUri string `json:"jwks_uri"` ++ UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` ++ EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` ++ IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` ++ RevocationEndpoint string `json:"revocation_endpoint,omitempty"` ++ IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` ++ RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` ++ ResponseTypesSupported []string `json:"response_types_supported"` ++ GrantTypesSupported []string `json:"grant_types_supported"` ++ CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` ++ IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` ++ ScopesSupported []string `json:"scopes_supported"` ++ TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` ++ ClaimsSupported []string `json:"claims_supported"` ++ SubjectTypesSupported []string `json:"subject_types_supported"` ++ RequestParameterSupported bool `json:"request_parameter_supported"` ++ ClaimsParameterSupported bool `json:"claims_parameter_supported"` + } + + // discoveryHandler implements http.Handler for GET /.well-known/openid-configuration. +@@ -84,6 +90,15 @@ + SubjectTypesSupported: []string{"public"}, + RequestParameterSupported: false, + ClaimsParameterSupported: false, ++ } ++ if cfg.IntrospectionEndpoint != "" { ++ d.IntrospectionEndpoint = cfg.IntrospectionEndpoint ++ d.IntrospectionEndpointAuthMethodsSupported = []string{"client_secret_basic"} ++ d.ClaimsSupported = append(d.ClaimsSupported, "jti") ++ } ++ if cfg.RevocationEndpoint != "" { ++ d.RevocationEndpoint = cfg.RevocationEndpoint ++ d.RevocationEndpointAuthMethodsSupported = []string{"client_secret_basic", "none"} + } + + b, err := json.Marshal(d) +diff --git a/src/internal/server/oidc/token.go b/src/internal/server/oidc/token.go +--- a/src/internal/server/oidc/token.go ++++ b/src/internal/server/oidc/token.go +@@ -32,6 +32,9 @@ + // TenantEngine sources the optional tenant_roles claim (KEY-WP-0005-T02). + // Nil disables it entirely -- token issuance never depends on it. + TenantEngine *tenantengine.Client ++ // Tokens enables issuer-owned online token status; nil retains the legacy ++ // expiry-only profile. Enabling it requires explicit deployment admission. ++ Tokens *TokenStatusStore + } + + // tokenResponse is the JSON body returned on a successful token exchange. +@@ -233,10 +236,24 @@ + claims["aud"] = audience + } + claims["scope"] = strings.Join(sess.Scopes, " ") ++ if h.Tokens != nil { ++ id := make([]byte, 32) ++ if _, err := rand.Read(id); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } ++ claims["jti"] = base64.RawURLEncoding.EncodeToString(id) ++ } + accessToken, err := buildJWT(claims, kid, h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return ++ } ++ if h.Tokens != nil { ++ if err := h.Tokens.register(accessToken, client, time.Unix(exp.Unix(), 0), now); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } + } + + // 8. Build response. The session was already consumed at lookup, so no +@@ -357,10 +374,24 @@ + if roles, ok := h.TenantEngine.Roles(ctx, client.Tenant); ok { + claims["tenant_roles"] = roles + } ++ if h.Tokens != nil { ++ id := make([]byte, 32) ++ if _, err := rand.Read(id); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } ++ claims["jti"] = base64.RawURLEncoding.EncodeToString(id) ++ } + jwtToken, err := buildJWT(claims, "key-1", h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return ++ } ++ if h.Tokens != nil { ++ if err := h.Tokens.register(jwtToken, client, time.Unix(now.Add(tokenLifetime).Unix(), 0), now); err != nil { ++ http.Error(w, "token status unavailable", http.StatusServiceUnavailable) ++ return ++ } + } + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: now, EventType: telemetry.EventTokenIssued, ClientID: clientID, +diff --git a/src/internal/server/oidc/token_status.go b/src/internal/server/oidc/token_status.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status.go +@@ -0,0 +1,97 @@ ++package oidc ++ ++import ( ++ "crypto/sha256" ++ "encoding/json" ++ "errors" ++ "sync" ++ "time" ++ ++ "keycape/internal/domain" ++) ++ ++// TokenStatusStore is the optional single-process online access-token ledger. ++// It holds only token hashes and registration commitments, never bearer values. ++// Losing this store makes every previously issued token inactive to online ++// checks. It is not a browser session, approval, or operation authority store. ++type TokenStatusStore struct { ++ mu sync.Mutex ++ entries map[[32]byte]tokenStatus ++ limit int ++} ++ ++type tokenStatus struct { ++ clientID string ++ registration [32]byte ++ expires time.Time ++ revoked bool ++} ++ ++func NewTokenStatusStore(limit int) (*TokenStatusStore, error) { ++ if limit < 1 || limit > 1000000 { ++ return nil, errors.New("invalid token-status capacity") ++ } ++ return &TokenStatusStore{entries: make(map[[32]byte]tokenStatus), limit: limit}, nil ++} ++ ++func registrationCommitment(client *domain.Client) [32]byte { ++ // ClientSecret and TokenLifetime are deliberately omitted from Client JSON. ++ // Bind their commitments explicitly as well as all public registration data. ++ secret := sha256.Sum256([]byte(client.ClientSecret)) ++ raw, _ := json.Marshal(struct { ++ Client *domain.Client ++ Secret [32]byte ++ Lifetime time.Duration ++ }{client, secret, client.TokenLifetime}) ++ return sha256.Sum256(raw) ++} ++ ++func (s *TokenStatusStore) register(raw string, client *domain.Client, expires, now time.Time) error { ++ if s == nil || client == nil || raw == "" || len(raw) > 32768 || !now.Before(expires) { ++ return errors.New("token-status registration refused") ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ for key, entry := range s.entries { ++ if !now.Before(entry.expires) { ++ delete(s.entries, key) ++ } ++ } ++ key := sha256.Sum256([]byte(raw)) ++ if _, exists := s.entries[key]; exists || len(s.entries) >= s.limit { ++ return errors.New("token-status registration refused") ++ } ++ s.entries[key] = tokenStatus{clientID: client.ClientID, registration: registrationCommitment(client), expires: expires} ++ return nil ++} ++ ++func (s *TokenStatusStore) active(raw string, clients map[string]*domain.Client, now time.Time) (string, bool) { ++ if s == nil { ++ return "", false ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ entry, ok := s.entries[sha256.Sum256([]byte(raw))] ++ if !ok || entry.revoked || !now.Before(entry.expires) { ++ return "", false ++ } ++ client := clients[entry.clientID] ++ if client == nil || client.ClientID != entry.clientID || registrationCommitment(client) != entry.registration { ++ return "", false ++ } ++ return entry.clientID, true ++} ++ ++func (s *TokenStatusStore) revoke(raw, clientID string) { ++ if s == nil { ++ return ++ } ++ s.mu.Lock() ++ defer s.mu.Unlock() ++ key := sha256.Sum256([]byte(raw)) ++ if entry, ok := s.entries[key]; ok && entry.clientID == clientID { ++ // Retain the tombstone until expiry: a retry can never reactivate it. ++ entry.revoked = true ++ s.entries[key] = entry ++ } ++} +diff --git a/src/internal/server/oidc/token_status_discovery_test.go b/src/internal/server/oidc/token_status_discovery_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_discovery_test.go +@@ -0,0 +1,30 @@ ++package oidc_test ++ ++import ( ++ "encoding/json" ++ "net/http" ++ "net/http/httptest" ++ "testing" ++ ++ "keycape/internal/server/oidc" ++) ++ ++func TestTokenStatusDiscoveryOnlyAdvertisesEnabledProfile(t *testing.T) { ++ for _, enabled := range []bool{false, true} { ++ cfg := oidc.DiscoveryConfig{Issuer: "https://issuer.example"} ++ if enabled { ++ cfg.IntrospectionEndpoint, cfg.RevocationEndpoint = cfg.Issuer+"/introspect", cfg.Issuer+"/revoke" ++ } ++ w := httptest.NewRecorder() ++ oidc.NewDiscoveryHandler(cfg).ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/.well-known/openid-configuration", nil)) ++ var doc map[string]interface{} ++ if json.Unmarshal(w.Body.Bytes(), &doc) != nil { ++ t.Fatal("discovery is invalid JSON") ++ } ++ for _, name := range []string{"introspection_endpoint", "revocation_endpoint", "introspection_endpoint_auth_methods_supported", "revocation_endpoint_auth_methods_supported"} { ++ if _, exists := doc[name]; exists != enabled { ++ t.Fatal("unimplemented token status advertised") ++ } ++ } ++ } ++} +diff --git a/src/internal/server/oidc/token_status_http.go b/src/internal/server/oidc/token_status_http.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_http.go +@@ -0,0 +1,145 @@ ++package oidc ++ ++import ( ++ "crypto/rsa" ++ "encoding/json" ++ "mime" ++ "net/http" ++ "time" ++ ++ "keycape/internal/domain" ++) ++ ++// TokenStatusHandler serves the optional issuer-owned RFC 7662 introspection ++// and RFC 7009 revocation endpoints. Introspection authenticates the issuing ++// confidential client; this profile does not delegate cross-client token reads. ++type TokenStatusHandler struct { ++ Tokens *TokenStatusStore ++ ClientConfig map[string]*domain.Client ++ Users domain.UserRepository ++ SigningKey *rsa.PublicKey ++ Issuer string ++} ++ ++func statusError(w http.ResponseWriter, status int, code string) { ++ w.Header().Set("Content-Type", "application/json") ++ w.Header().Set("Cache-Control", "no-store") ++ w.Header().Set("Pragma", "no-cache") ++ w.WriteHeader(status) ++ _ = json.NewEncoder(w).Encode(map[string]string{"error": code}) ++} ++ ++func (h *TokenStatusHandler) parse(w http.ResponseWriter, r *http.Request, introspection bool) (*domain.Client, string, bool) { ++ w.Header().Set("Cache-Control", "no-store") ++ w.Header().Set("Pragma", "no-cache") ++ if r.Method != http.MethodPost { ++ w.Header().Set("Allow", "POST") ++ statusError(w, http.StatusMethodNotAllowed, "invalid_request") ++ return nil, "", false ++ } ++ if h.Tokens == nil || h.SigningKey == nil || h.Issuer == "" { ++ statusError(w, http.StatusServiceUnavailable, "temporarily_unavailable") ++ return nil, "", false ++ } ++ media, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) ++ if err != nil || media != "application/x-www-form-urlencoded" || r.URL.RawQuery != "" || len(r.Header.Values("Authorization")) > 1 { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ r.Body = http.MaxBytesReader(w, r.Body, 65536) ++ if err := r.ParseForm(); err != nil { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ for _, values := range r.PostForm { ++ if len(values) != 1 { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ } ++ raw := r.PostForm.Get("token") ++ if raw == "" || len(raw) > 32768 { ++ statusError(w, http.StatusBadRequest, "invalid_request") ++ return nil, "", false ++ } ++ id, secret, basic := basicClientCredentials(r) ++ formID := r.PostForm.Get("client_id") ++ if !basic { ++ id = formID ++ } ++ client := h.ClientConfig[id] ++ valid := client != nil && client.ClientID == id && !r.PostForm.Has("client_secret") ++ if valid && client.ClientType == "confidential" { ++ valid = basic && (formID == "" || formID == id) && client.ClientSecret != "" && secretsEqual(secret, client.ClientSecret) ++ } else { ++ // Public-client revocation requires possession of its own bearer token. ++ // Public clients can never enumerate token status via introspection. ++ valid = valid && !introspection && client.ClientType == "public" && !basic && r.Header.Get("Authorization") == "" ++ } ++ if !valid { ++ w.Header().Set("WWW-Authenticate", `Basic realm="keycape-token-status"`) ++ statusError(w, http.StatusUnauthorized, "invalid_client") ++ return nil, "", false ++ } ++ return client, raw, true ++} ++ ++func (h *TokenStatusHandler) Introspect(w http.ResponseWriter, r *http.Request) { ++ client, raw, ok := h.parse(w, r, true) ++ if !ok { ++ return ++ } ++ response := map[string]interface{}{"active": false} ++ defer func() { ++ w.Header().Set("Content-Type", "application/json") ++ _ = json.NewEncoder(w).Encode(response) ++ }() ++ id, active := h.Tokens.active(raw, h.ClientConfig, time.Now()) ++ if !active || id != client.ClientID { ++ return ++ } ++ claims, err := validateAccessToken(raw, h.SigningKey, h.Issuer) ++ if err != nil { ++ return ++ } ++ sub, _ := claims["sub"].(string) ++ switch claims["principal_type"] { ++ case "human": ++ if h.Users == nil { ++ return ++ } ++ user, err := lookupUserBySubject(r.Context(), h.Users, claims, sub) ++ if err != nil || !user.Enabled || isSuspended(user) { ++ return ++ } ++ case "service": ++ if sub == "" || sub != client.ServiceSubject { ++ return ++ } ++ default: ++ return ++ } ++ // Directory lookup may block. Recheck revocation and registration after it. ++ if current, active := h.Tokens.active(raw, h.ClientConfig, time.Now()); !active || current != id { ++ return ++ } ++ response["active"] = true ++ response["client_id"] = id ++ response["token_type"] = "Bearer" ++ for _, field := range []string{"iss", "sub", "aud", "scope", "iat", "exp", "jti"} { ++ if value, present := claims[field]; present { ++ response[field] = value ++ } ++ } ++} ++ ++func (h *TokenStatusHandler) Revoke(w http.ResponseWriter, r *http.Request) { ++ client, raw, ok := h.parse(w, r, false) ++ if !ok { ++ return ++ } ++ // Unknown, expired, ID and other-client tokens receive the same empty ++ // success as an already-revoked token; no existence information leaks. ++ h.Tokens.revoke(raw, client.ClientID) ++ w.WriteHeader(http.StatusOK) ++} +diff --git a/src/internal/server/oidc/token_status_internal_test.go b/src/internal/server/oidc/token_status_internal_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_internal_test.go +@@ -0,0 +1,66 @@ ++package oidc ++ ++import ( ++ "fmt" ++ "sync" ++ "testing" ++ "time" ++ ++ "keycape/internal/domain" ++) ++ ++func TestTokenStatusExactExpiryCapacityAndTombstones(t *testing.T) { ++ client := &domain.Client{ClientID: "synthetic-client", ClientType: "public"} ++ clients := map[string]*domain.Client{client.ClientID: client} ++ store, _ := NewTokenStatusStore(1) ++ now := time.Unix(1700000000, 0) ++ end := now.Add(time.Second) ++ if store.register("synthetic-first", client, end, now) != nil { ++ t.Fatal("register refused") ++ } ++ if _, active := store.active("synthetic-first", clients, end); active { ++ t.Fatal("expiry equality accepted") ++ } ++ store.revoke("synthetic-first", client.ClientID) ++ if store.register("synthetic-first", client, end, now) == nil { ++ t.Fatal("revoked token reactivated") ++ } ++ if store.register("synthetic-second", client, end.Add(time.Second), end) != nil { ++ t.Fatal("expired tombstone not collected") ++ } ++ if _, active := store.active("synthetic-second", clients, end); !active { ++ t.Fatal("fresh token refused") ++ } ++ for _, capacity := range []int{-1, 0, 1000001} { ++ if _, err := NewTokenStatusStore(capacity); err == nil { ++ t.Fatal("unbounded capacity accepted") ++ } ++ } ++} ++ ++func TestTokenStatusConcurrentIssueReadAndRevoke(t *testing.T) { ++ store, _ := NewTokenStatusStore(128) ++ client := &domain.Client{ClientID: "synthetic-client", ClientType: "public"} ++ clients := map[string]*domain.Client{client.ClientID: client} ++ now := time.Now() ++ var wg sync.WaitGroup ++ for i := 0; i < 64; i++ { ++ wg.Add(1) ++ go func(i int) { ++ defer wg.Done() ++ raw := fmt.Sprintf("synthetic-token-%d", i) ++ if store.register(raw, client, now.Add(time.Minute), now) != nil { ++ t.Error("concurrent registration failed") ++ return ++ } ++ if _, active := store.active(raw, clients, now); !active { ++ t.Error("concurrent current token refused") ++ } ++ store.revoke(raw, client.ClientID) ++ if _, active := store.active(raw, clients, now); active { ++ t.Error("concurrent revoked token accepted") ++ } ++ }(i) ++ } ++ wg.Wait() ++} +diff --git a/src/internal/server/oidc/token_status_test.go b/src/internal/server/oidc/token_status_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_test.go +@@ -0,0 +1,379 @@ ++package oidc_test ++ ++import ( ++ "context" ++ "encoding/json" ++ "io" ++ "net/http" ++ "net/http/httptest" ++ "net/url" ++ "strings" ++ "testing" ++ "time" ++ ++ "keycape/internal/domain" ++ "keycape/internal/server/oidc" ++ "keycape/internal/server/telemetry" ++) ++ ++type statusFixture struct { ++ token *oidc.TokenHandler ++ status *oidc.TokenStatusHandler ++ userinfo *oidc.UserinfoHandler ++ users *mockUserRepo ++} ++ ++func newStatusFixture(t *testing.T, capacity int) *statusFixture { ++ t.Helper() ++ users := &mockUserRepo{users: map[string]*domain.User{"alice": aliceUser()}} ++ tokens, err := oidc.NewTokenStatusStore(capacity) ++ if err != nil { ++ t.Fatal("store creation failed") ++ } ++ h, key := newTokenHandler(t, oidc.NewSessionStore(), users) ++ h.Tokens = tokens ++ h.ClientConfig["test-client"].ClientType = "confidential" ++ h.ClientConfig["test-client"].ClientSecret = "synthetic-status-only" ++ h.ClientConfig["other"] = &domain.Client{ClientID: "other", ClientType: "confidential", ClientSecret: "synthetic-other-only"} ++ return &statusFixture{token: h, users: users, ++ status: &oidc.TokenStatusHandler{Tokens: tokens, ClientConfig: h.ClientConfig, Users: users, SigningKey: &key.PublicKey, Issuer: h.Issuer}, ++ userinfo: &oidc.UserinfoHandler{Tokens: tokens, ClientConfig: h.ClientConfig, Users: users, SigningKey: &key.PublicKey, Issuer: h.Issuer, Emitter: telemetry.NoopEmitter{}}, ++ } ++} ++ ++func (f *statusFixture) issue(t *testing.T) (string, string, int) { ++ t.Helper() ++ verifier, _ := makeVerifierAndChallenge() ++ code := seededSession(f.token.Sessions, verifier) ++ req := tokenRequest(url.Values{"grant_type": {"authorization_code"}, "client_id": {"test-client"}, "code": {code}, "code_verifier": {verifier}}) ++ if f.token.ClientConfig["test-client"].ClientType == "confidential" { ++ req.SetBasicAuth(url.QueryEscape("test-client"), url.QueryEscape(f.token.ClientConfig["test-client"].ClientSecret)) ++ } ++ w := httptest.NewRecorder() ++ f.token.ServeHTTP(w, req) ++ if w.Code != http.StatusOK { ++ return "", "", w.Code ++ } ++ var body struct { ++ Access string `json:"access_token"` ++ ID string `json:"id_token"` ++ } ++ if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Access == "" || body.ID == "" { ++ t.Fatal("invalid token response") ++ } ++ return body.Access, body.ID, w.Code ++} ++ ++func (f *statusFixture) request(t *testing.T, raw, client, secret string, revoke bool, mutate func(*http.Request)) *httptest.ResponseRecorder { ++ t.Helper() ++ form := url.Values{"token": {raw}} ++ if secret == "" { ++ form.Set("client_id", client) ++ } ++ r := httptest.NewRequest(http.MethodPost, "/introspect", strings.NewReader(form.Encode())) ++ r.Header.Set("Content-Type", "application/x-www-form-urlencoded") ++ if secret != "" { ++ r.SetBasicAuth(url.QueryEscape(client), url.QueryEscape(secret)) ++ } ++ if mutate != nil { ++ mutate(r) ++ } ++ w := httptest.NewRecorder() ++ if revoke { ++ f.status.Revoke(w, r) ++ } else { ++ f.status.Introspect(w, r) ++ } ++ if w.Header().Get("Cache-Control") != "no-store" || w.Header().Get("Pragma") != "no-cache" { ++ t.Fatal("missing private response controls") ++ } ++ return w ++} ++ ++func (f *statusFixture) introspect(t *testing.T, raw string) map[string]interface{} { ++ t.Helper() ++ w := f.request(t, raw, "test-client", f.token.ClientConfig["test-client"].ClientSecret, false, nil) ++ if w.Code != http.StatusOK { ++ t.Fatalf("introspection status %d", w.Code) ++ } ++ var body map[string]interface{} ++ if json.Unmarshal(w.Body.Bytes(), &body) != nil { ++ t.Fatal("invalid introspection response") ++ } ++ return body ++} ++ ++func (f *statusFixture) userinfoStatus(raw string) int { ++ w := httptest.NewRecorder() ++ f.userinfo.ServeHTTP(w, userinfoRequest(raw)) ++ return w.Code ++} ++ ++func TestTokenStatusActualIssueRevokeAndFreshGrant(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ first, id, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatalf("issue status %d", code) ++ } ++ active := f.introspect(t, first) ++ if active["active"] != true || active["sub"] != "user-alice" || active["client_id"] != "test-client" || active["token_type"] != "Bearer" { ++ t.Fatal("wrong active-token metadata") ++ } ++ if _, present := parseJWTPayload(t, id)["jti"]; present { ++ t.Fatal("access-token lifecycle changed ID token") ++ } ++ if f.userinfoStatus(first) != http.StatusOK { ++ t.Fatal("issued token refused") ++ } ++ second, _, code := f.issue(t) ++ if code != http.StatusOK || first == second || active["jti"] == f.introspect(t, second)["jti"] { ++ t.Fatal("fresh grants must be distinct") ++ } ++ for i := 0; i < 2; i++ { ++ w := f.request(t, first, "test-client", "synthetic-status-only", true, nil) ++ if w.Code != http.StatusOK || w.Body.Len() != 0 { ++ t.Fatal("revocation must be empty idempotent success") ++ } ++ } ++ if got := f.introspect(t, first); got["active"] != false || len(got) != 1 { ++ t.Fatal("revoked metadata leaked or remained active") ++ } ++ if f.userinfoStatus(first) != http.StatusUnauthorized { ++ t.Fatal("revoked token still accepted by real UserInfo") ++ } ++ if f.introspect(t, second)["active"] != true || f.userinfoStatus(second) != http.StatusOK { ++ t.Fatal("revoking one grant affected another") ++ } ++ third, _, code := f.issue(t) ++ if code != http.StatusOK || f.introspect(t, third)["active"] != true || f.introspect(t, first)["active"] != false { ++ t.Fatal("fresh issuance reactivated a revoked grant") ++ } ++} ++ ++func TestTokenStatusNoCrossClientOrIDTokenAuthority(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, id, _ := f.issue(t) ++ for _, invalid := range []string{id, "unknown.synthetic.token", raw + "changed"} { ++ got := f.introspect(t, invalid) ++ if len(got) != 1 || got["active"] != false { ++ t.Fatal("non-access token disclosed metadata") ++ } ++ w := f.request(t, invalid, "test-client", "synthetic-status-only", true, nil) ++ if w.Code != http.StatusOK || w.Body.Len() != 0 { ++ t.Fatal("unknown revocation disclosed state") ++ } ++ } ++ w := f.request(t, raw, "other", "synthetic-other-only", false, nil) ++ if w.Code != http.StatusOK || strings.TrimSpace(w.Body.String()) != `{"active":false}` { ++ t.Fatal("other client read token") ++ } ++ w = f.request(t, raw, "other", "synthetic-other-only", true, nil) ++ if w.Code != http.StatusOK || f.introspect(t, raw)["active"] != true { ++ t.Fatal("other client revoked token") ++ } ++} ++ ++func TestTokenStatusCapacityFailsIssuanceWithoutEvictingLiveToken(t *testing.T) { ++ f := newStatusFixture(t, 1) ++ raw, _, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatal("first issuance failed") ++ } ++ if _, _, code = f.issue(t); code != http.StatusServiceUnavailable { ++ t.Fatalf("full store issue status %d", code) ++ } ++ if f.introspect(t, raw)["active"] != true { ++ t.Fatal("capacity evicted live authority") ++ } ++ f.request(t, raw, "test-client", "synthetic-status-only", true, nil) ++ if _, _, code = f.issue(t); code != http.StatusServiceUnavailable { ++ t.Fatal("revocation tombstone was evicted before expiry") ++ } ++} ++ ++func TestTokenStatusRestartAndUnavailableStoreRefuse(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ empty, err := oidc.NewTokenStatusStore(8) ++ if err != nil { ++ t.Fatal(err) ++ } ++ f.status.Tokens, f.userinfo.Tokens = empty, empty ++ if f.introspect(t, raw)["active"] != false || f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("restart resurrected unknown token") ++ } ++ f.status.Tokens = nil ++ if w := f.request(t, raw, "test-client", "synthetic-status-only", false, nil); w.Code != http.StatusServiceUnavailable { ++ t.Fatal("missing store allowed online status") ++ } ++ // Offline JWT validity remains a separate legacy contract. Do not claim ++ // that adding an endpoint revokes tokens in consumers that never consult it. ++ f.userinfo.Tokens = nil ++ if f.userinfoStatus(raw) != http.StatusOK { ++ t.Fatal("legacy profile changed") ++ } ++} ++ ++func TestTokenStatusRegistrationDriftAndDirectoryDisposition(t *testing.T) { ++ for _, change := range []string{"removed", "audience", "secret", "scopes", "lifetime", "suspended", "disabled", "user-removed"} { ++ t.Run(change, func(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ switch change { ++ case "removed": ++ delete(f.token.ClientConfig, "test-client") ++ case "audience": ++ f.token.ClientConfig["test-client"].Audience = "different-resource" ++ case "secret": ++ f.token.ClientConfig["test-client"].ClientSecret = "replacement-synthetic-only" ++ case "scopes": ++ f.token.ClientConfig["test-client"].AllowedScopes = []string{"openid"} ++ case "lifetime": ++ f.token.ClientConfig["test-client"].TokenLifetime = time.Minute ++ case "suspended": ++ f.users.users["alice"].Groups = []string{"netkingdom-suspended"} ++ case "disabled": ++ f.users.users["alice"].Enabled = false ++ case "user-removed": ++ delete(f.users.users, "alice") ++ } ++ if f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("UserInfo accepted changed authority") ++ } ++ if change != "removed" && f.introspect(t, raw)["active"] != false { ++ t.Fatal("introspection accepted changed authority") ++ } ++ }) ++ } ++} ++ ++func TestTokenStatusPublicClientMayRevokeButNotIntrospect(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ f.token.ClientConfig["test-client"].ClientType = "public" ++ f.token.ClientConfig["test-client"].ClientSecret = "" ++ raw, _, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatal("public PKCE issue failed") ++ } ++ if w := f.request(t, raw, "test-client", "", false, nil); w.Code != http.StatusUnauthorized { ++ t.Fatal("public client enumerated status") ++ } ++ if w := f.request(t, raw, "test-client", "", true, nil); w.Code != http.StatusOK { ++ t.Fatal("public bearer revocation failed") ++ } ++ if f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("public revocation not enforced") ++ } ++} ++ ++func TestTokenStatusServiceGrantIsCurrentAndDistinct(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ client := f.token.ClientConfig["test-client"] ++ client.GrantTypes, client.ServiceSubject, client.Tenant = []string{"client_credentials"}, "service:status-fixture", "tenant:fixture" ++ issue := func() string { ++ r := tokenRequest(url.Values{"grant_type": {"client_credentials"}}) ++ r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret)) ++ w := httptest.NewRecorder() ++ f.token.ServeHTTP(w, r) ++ if w.Code != http.StatusOK { ++ t.Fatalf("service issue status %d", w.Code) ++ } ++ var body struct { ++ Access string `json:"access_token"` ++ } ++ if json.Unmarshal(w.Body.Bytes(), &body) != nil || body.Access == "" { ++ t.Fatal("service response invalid") ++ } ++ return body.Access ++ } ++ first, second := issue(), issue() ++ if first == second || f.introspect(t, first)["active"] != true { ++ t.Fatal("service grants not distinct/current") ++ } ++ f.request(t, first, client.ClientID, client.ClientSecret, true, nil) ++ if f.introspect(t, first)["active"] != false || f.introspect(t, second)["active"] != true { ++ t.Fatal("service revocation not token-scoped") ++ } ++} ++ ++func TestTokenStatusRejectsMalformedOrUnauthenticatedRequests(t *testing.T) { ++ for _, kind := range []string{"get", "query", "json", "duplicate-token", "oversize", "wrong-secret", "missing-auth", "duplicate-auth", "body-secret", "mismatched-client"} { ++ t.Run(kind, func(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ mutate := func(r *http.Request) { ++ body := func(form url.Values) { r.Body = io.NopCloser(strings.NewReader(form.Encode())) } ++ switch kind { ++ case "get": ++ r.Method = http.MethodGet ++ case "query": ++ r.URL.RawQuery = "token=must-never-use-url" ++ case "json": ++ r.Header.Set("Content-Type", "application/json") ++ case "duplicate-token": ++ body(url.Values{"token": {raw, raw}}) ++ case "oversize": ++ body(url.Values{"token": {strings.Repeat("x", 65537)}}) ++ case "wrong-secret": ++ r.SetBasicAuth("test-client", "wrong-synthetic-secret") ++ case "missing-auth": ++ r.Header.Del("Authorization") ++ case "duplicate-auth": ++ r.Header.Add("Authorization", "Basic extra") ++ case "body-secret": ++ body(url.Values{"token": {raw}, "client_secret": {"unsupported"}}) ++ case "mismatched-client": ++ body(url.Values{"token": {raw}, "client_id": {"other"}}) ++ } ++ } ++ for _, revoke := range []bool{false, true} { ++ w := f.request(t, raw, "test-client", "synthetic-status-only", revoke, mutate) ++ if w.Code < 400 { ++ t.Fatal("malformed request accepted") ++ } ++ } ++ if f.introspect(t, raw)["active"] != true { ++ t.Fatal("refused request changed token") ++ } ++ }) ++ } ++} ++ ++type blockingStatusUsers struct { ++ *mockUserRepo ++ entered, release chan struct{} ++} ++ ++func (u *blockingStatusUsers) LookupUser(ctx context.Context, name string) (*domain.User, error) { ++ close(u.entered) ++ select { ++ case <-u.release: ++ return u.mockUserRepo.LookupUser(ctx, name) ++ case <-ctx.Done(): ++ return nil, ctx.Err() ++ } ++} ++ ++func TestTokenStatusRevocationDuringDirectoryLookupRefuses(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ users := &blockingStatusUsers{mockUserRepo: f.users, entered: make(chan struct{}), release: make(chan struct{})} ++ f.status.Users = users ++ done := make(chan *httptest.ResponseRecorder, 1) ++ go func() { done <- f.request(t, raw, "test-client", "synthetic-status-only", false, nil) }() ++ select { ++ case <-users.entered: ++ case <-time.After(3 * time.Second): ++ t.Fatal("directory lookup not entered") ++ } ++ f.request(t, raw, "test-client", "synthetic-status-only", true, nil) ++ close(users.release) ++ select { ++ case w := <-done: ++ if strings.TrimSpace(w.Body.String()) != `{"active":false}` { ++ t.Fatal("delayed lookup resurrected revoked token") ++ } ++ case <-time.After(3 * time.Second): ++ t.Fatal("introspection did not finish") ++ } ++} +diff --git a/src/internal/server/oidc/token_status_transport_test.go b/src/internal/server/oidc/token_status_transport_test.go +--- /dev/null ++++ b/src/internal/server/oidc/token_status_transport_test.go +@@ -0,0 +1,121 @@ ++package oidc_test ++ ++import ( ++ "encoding/json" ++ "io" ++ "net/http" ++ "net/http/httptest" ++ "net/url" ++ "strings" ++ "testing" ++ "time" ++) ++ ++func TestTokenStatusActualHTTPSFlowAndAuthenticationEncoding(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ f.token.ClientConfig["test-client"].ClientSecret = "synthetic + / colon: only" ++ mux := http.NewServeMux() ++ mux.Handle("/token", f.token) ++ mux.Handle("/userinfo", f.userinfo) ++ mux.HandleFunc("/introspect", f.status.Introspect) ++ mux.HandleFunc("/revoke", f.status.Revoke) ++ server := httptest.NewTLSServer(mux) ++ defer server.Close() ++ client := server.Client() ++ verifier, _ := makeVerifierAndChallenge() ++ code := seededSession(f.token.Sessions, verifier) ++ post := func(path string, form url.Values) (int, []byte) { ++ r, err := http.NewRequest(http.MethodPost, server.URL+path, strings.NewReader(form.Encode())) ++ if err != nil { ++ t.Fatal("request construction failed") ++ } ++ r.Header.Set("Content-Type", "application/x-www-form-urlencoded") ++ r.SetBasicAuth(url.QueryEscape("test-client"), url.QueryEscape(f.token.ClientConfig["test-client"].ClientSecret)) ++ resp, err := client.Do(r) ++ if err != nil { ++ t.Fatal("HTTPS fixture transport failed") ++ } ++ defer resp.Body.Close() ++ body, err := io.ReadAll(io.LimitReader(resp.Body, 65537)) ++ if err != nil || len(body) > 65536 { ++ t.Fatal("response read failed") ++ } ++ return resp.StatusCode, body ++ } ++ status, body := post("/token", url.Values{"grant_type": {"authorization_code"}, "client_id": {"test-client"}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {seededRedirectURI}}) ++ var tokens struct { ++ Access string `json:"access_token"` ++ } ++ if status != http.StatusOK || json.Unmarshal(body, &tokens) != nil || tokens.Access == "" { ++ t.Fatal("actual HTTPS PKCE exchange failed") ++ } ++ // A misleading type hint must not hide a supported access token. ++ form := url.Values{"token": {tokens.Access}, "token_type_hint": {"refresh_token"}} ++ status, body = post("/introspect", form) ++ var metadata map[string]interface{} ++ if status != http.StatusOK || json.Unmarshal(body, &metadata) != nil || metadata["active"] != true { ++ t.Fatal("actual HTTPS introspection failed") ++ } ++ if status, body = post("/revoke", form); status != http.StatusOK || len(body) != 0 { ++ t.Fatal("actual HTTPS revocation failed") ++ } ++ status, body = post("/introspect", form) ++ if status != http.StatusOK || strings.TrimSpace(string(body)) != `{"active":false}` { ++ t.Fatal("revocation did not affect HTTPS introspection") ++ } ++ r, _ := http.NewRequest(http.MethodGet, server.URL+"/userinfo", nil) ++ r.Header.Set("Authorization", "Bearer "+tokens.Access) ++ resp, err := client.Do(r) ++ if err != nil { ++ t.Fatal("HTTPS UserInfo failed") ++ } ++ defer resp.Body.Close() ++ if resp.StatusCode != http.StatusUnauthorized || resp.Header.Get("Cache-Control") != "no-store" { ++ t.Fatal("real UserInfo accepted or cached revoked bearer") ++ } ++} ++ ++func TestTokenStatusRevocationDuringUserInfoLookupRefuses(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ raw, _, _ := f.issue(t) ++ users := &blockingStatusUsers{mockUserRepo: f.users, entered: make(chan struct{}), release: make(chan struct{})} ++ f.userinfo.Users = users ++ done := make(chan int, 1) ++ go func() { done <- f.userinfoStatus(raw) }() ++ select { ++ case <-users.entered: ++ case <-time.After(3 * time.Second): ++ t.Fatal("UserInfo did not enter directory lookup") ++ } ++ f.request(t, raw, "test-client", "synthetic-status-only", true, nil) ++ close(users.release) ++ select { ++ case code := <-done: ++ if code != http.StatusUnauthorized { ++ t.Fatal("delayed UserInfo lookup resurrected revoked token") ++ } ++ case <-time.After(3 * time.Second): ++ t.Fatal("UserInfo did not finish") ++ } ++} ++ ++func TestTokenStatusLegacyGrantHasNoStatusClaim(t *testing.T) { ++ f := newStatusFixture(t, 8) ++ f.token.Tokens = nil ++ raw, id, code := f.issue(t) ++ if code != http.StatusOK { ++ t.Fatal("legacy issue failed") ++ } ++ for _, token := range []string{raw, id} { ++ if _, present := parseJWTPayload(t, token)["jti"]; present { ++ t.Fatal("disabled token contract changed") ++ } ++ } ++ if f.introspect(t, raw)["active"] != false || f.userinfoStatus(raw) != http.StatusUnauthorized { ++ t.Fatal("unregistered legacy token gained online authority") ++ } ++ f.userinfo.Tokens = nil ++ if f.userinfoStatus(raw) != http.StatusOK { ++ t.Fatal("legacy token no longer accepted in legacy mode") ++ } ++} +diff --git a/src/internal/server/oidc/userinfo.go b/src/internal/server/oidc/userinfo.go +--- a/src/internal/server/oidc/userinfo.go ++++ b/src/internal/server/oidc/userinfo.go +@@ -22,15 +22,21 @@ + // the user, and returns claims that are consistent with those in the ID token + // for the same scope set. + type UserinfoHandler struct { +- Users domain.UserRepository +- SigningKey *rsa.PublicKey // used to verify the incoming access token +- Issuer string +- Emitter telemetry.Emitter ++ Users domain.UserRepository ++ SigningKey *rsa.PublicKey // used to verify the incoming access token ++ Issuer string ++ Emitter telemetry.Emitter ++ Tokens *TokenStatusStore ++ ClientConfig map[string]*domain.Client + } + + // ServeHTTP handles GET /userinfo. + func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() ++ if h.Tokens != nil { ++ w.Header().Set("Cache-Control", "no-store") ++ w.Header().Set("Pragma", "no-cache") ++ } + + // 1. Extract Bearer token. + tokenStr, ok := bearerToken(r) +@@ -45,6 +51,12 @@ + if err != nil { + http.Error(w, `{"error":"invalid_token","description":"token validation failed"}`, http.StatusUnauthorized) + return ++ } ++ if h.Tokens != nil { ++ if _, active := h.Tokens.active(tokenStr, h.ClientConfig, time.Now()); !active { ++ http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized) ++ return ++ } + } + + // 3. Extract sub claim (which is the username in our model). +@@ -67,6 +79,12 @@ + if isSuspended(user) { + http.Error(w, `{"error":"invalid_token","description":"subject is suspended"}`, http.StatusUnauthorized) + return ++ } ++ if h.Tokens != nil { ++ if _, active := h.Tokens.active(tokenStr, h.ClientConfig, time.Now()); !user.Enabled || !active { ++ http.Error(w, `{"error":"invalid_token"}`, http.StatusUnauthorized) ++ return ++ } + } + + // 5. Build response claims filtered by the scopes embedded in the token. +diff --git a/workplans/KEY-WP-0042-online-access-token-status.md b/workplans/KEY-WP-0042-online-access-token-status.md +--- /dev/null ++++ b/workplans/KEY-WP-0042-online-access-token-status.md +@@ -0,0 +1,80 @@ ++--- ++id: KEY-WP-0042 ++type: workplan ++title: "Provide issuer-owned current access-token status and scoped revocation" ++domain: infotech ++repo: key-cape ++status: proposed ++owner: codex ++topic_slug: custodian ++created: "2026-10-18" ++updated: "2026-10-18" ++flavor: extension ++depends_on: [] ++--- ++ ++CUST-WP-0080's current-operation/inverse requirement exposed a concrete missing ++issuer facility. KEY-WP-0025-T03 truthfully documents the expiry-only limit; ++KEY-WP-0038 owns Forgejo's existing client, not a token-liveness API. This is an ++explicit proposed scope extension in the existing issuer, not a silent change ++to that completed limit record. No new service or duplicate session authority. ++Consumer binding/authorization and native case acceptance stay in ++NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09; they are not copied here. ++ ++## Review the bounded issuer/profile extension ++ ++```task ++id: KEY-WP-0042-T01 ++status: progress ++priority: high ++``` ++ ++The reviewable contract is `docs/online-token-status.md`: opt-in RFC 7662/7009 ++access-token status; registered owning-client authentication; private hash-only ++bounded state; unique access-token jti; no ID-token change; singleton store-loss ++refusal; disabled legacy behavior explicit. Source preparation is authorized by ++the full Custodian implementation goal. Independent issuer/profile/consumer ++assent remains required before admission; no such assent is inferred here. ++ ++## Implement and verify the actual issuer paths ++ ++```task ++id: KEY-WP-0042-T02 ++status: done ++priority: high ++``` ++ ++Wire the same issuer store to both actual grant types, UserInfo, introspection, ++revocation, configuration validation and implemented discovery. Verify distinct ++grants, exact expiry, replay/tombstones, capacity, malformed/unauthenticated and ++cross-client refusals, current registration and human disposition, revocation ++during directory lookup, process-store loss and absence of secret output. Keep ++the complete existing source suite, disabled profile and build passing. ++ ++## Admit and accept the issuer lifecycle at native scope ++ ++```task ++id: KEY-WP-0042-T03 ++status: todo ++priority: high ++``` ++ ++After independent contract review, admit exact issuer image/config/profile, ++HTTPS ingress/client custody and measured capacity/headroom. Exercise real ++human PKCE and service tokens, actual endpoint authentication, revoked-token ++UserInfo refusal, expiry, unavailable dependency, restart/store loss and fresh ++reauthentication. Preserve old offline-token semantics and document every ++consumer's selected mode. Review disabling/rollback explicitly; never silently ++fall back to offline acceptance for online-profile consumers. Provide owner ++receipts to existing NK48/Apps consumer tasks and Audit source admission. ++ ++2026-10-18 source verification: all 659 test/subtest results in the complete ++race suite pass, with six preexisting native/external skips. The standalone ++binary passes 26 isolated process checks, including actual main/router/config, ++service issuance, capacity, revocation, process restart, fresh issuance and ++disabled-profile behavior; fixture diagnostics omit synthetic private inputs. ++The HTTPS human PKCE flow and revocation during directory lookups also pass. ++An isolated control omitting the revoked-state predicate fails seven intended ++checks. This closes source task T02 only. T01 independent issuer/profile review, ++T03 native admission and all existing consumer operation/effect bindings remain ++open. Evidence is retained by CUST-WP-0080 under keycape-token-status-r1. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/SCOPE.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/SCOPE.md new file mode 100644 index 0000000..ab2522b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/SCOPE.md @@ -0,0 +1,222 @@ +# SCOPE + +Reviewed 2026-09-10 against source revision `f247d35`. + +Review lineage, because "what changed since?" should have a usable answer. The +full capability review was 2026-09-09 against `5ae80d3` +([reassessment](history/2026-09-09-scope-reassessment.md)), superseding +2026-09-05 against `b989de4`. `7a73352` followed it by a minute — documentation +and workplans only — and was confirmed by reading the commit rather than by +report. Since then the tenant claim gained `tenant_source`, the approver client +was registered, and two guards were added that pin properties rather than +behaviours; each is described below. Read versus told, and reviewed versus merely +later, are distinctions this header is meant to preserve. + +## Purpose and boundary + +KeyCape is Go identity **Tooling** for NetKingdom's lightweight IAM deployment. +It runs its own OIDC-style issuer, uses Authelia for upstream browser +authentication, reads identities from LLDAP, and delegates factor checks to +privacyIDEA. It also provides caller authentication commands, directory snapshot +validation, and migration artifact generators. + +It issues identity, role, scope and assurance claims. Resource authorization +belongs to access-engine and consuming services; secret custody and OpenBao +policy/token enforcement belong to their platform owners. The implemented subset +supports NetKingdom integrations, but complete profile conformance and drop-in +Keycloak interchangeability are not established. + +## Implemented capabilities + +| Surface | What the current source provides | +| --- | --- | +| Issuer HTTP API | Discovery, `/authorize` and its callback/registration-return routes, `/token`, `/jwks`, `/userinfo`, local `/logout`, `/healthz` (liveness, probes nothing) and `/readyz` (probes LLDAP by bind, Authelia and privacyIDEA). | +| Human authentication | Authorization code with S256 PKCE, exact registered redirects, scope allow-lists, Authelia login, privacyIDEA MFA challenges, client-specific assurance requirements, browser session reuse, step-up/fresh-login handling, and configured registration/enrollment handoffs. The exchange is bound to the redirect URI the code was issued for, refused for clients not registered for the grant, authenticated for confidential clients, and the code is consumed atomically so exactly one exchange can succeed. Upstream Authelia ID tokens are verified — signature against the provider's published keys, expected issuer, own client ID in the audience, validity window — failing closed and independently of transport. | +| Service authentication | Static confidential `client_credentials` clients authenticated with form-encoded `client_secret_basic`; configured subject, tenant, roles, scopes and per-client token lifetime. Secrets are resolved from environment references at startup. | +| Tokens and identity | Locally signed RS256 JWTs; configurable access-token resource audience while ID tokens retain the client audience; human/service principal types, tenant, groups, roles, scope and assurance claims. A service tenant is bound at registration; a human tenant comes from the directory, or from a client-declared zone when the directory has placed the user nowhere, and issuance is refused when the two conflict. `assurance.at` is the authentication time, not the mint time. UserInfo verifies algorithm, issuer and access-token purpose, resolves canonical directory subjects, and filters profile/email/groups by scope. | +| Caller commands | `keycape login` for public-client browser PKCE login, `keycape service-token` for service exchange, and `keycape verify-client` to check a live registration's subject, tenant, roles, lifetime and scope refusals without emitting values. HTTPS discovery/JWKS verification and private JSON token-file delivery outside Git; no token output on stdout. | +| Validation and migration | Canonical snapshot checks; deterministic LLDAP user/group/membership export that records whether it enumerated the whole directory; basic Keycloak realm JSON; LDIF generation for OpenLDAP, 389 Directory Server and AD targets. These generate artifacts rather than execute a complete migration. | +| Diagnostics and packaging | Structured authentication/enforcement/migration events, liveness and dependency-probing readiness endpoints, Go build/test/vet targets, a container packaging the issuer and all four migration/validation binaries, and development/CI scaffolding. | + +## Material limits + +- Client registrations, service secrets and the signing key are loaded at startup. + Login, authorization and handoff state are process-local. There is no shared + session store, general refresh-token flow, token introspection/revocation API, + or automatic signing-key/client-secret rotation service. Logout clears the + local KeyCape session, not every upstream or downstream session/token. +- The optional tenant-engine `tenant_roles` adapter is wired through a + `tenantEngine` config block and is off unless `baseURL` is set (KEY-WP-0024). + When enabled it fails open: an unreachable source omits the claim rather than + failing issuance, so `tenant_roles` is a cache and must not be trusted for + privileged decisions. +- The LLDAP export enumerates the group subtree directly, so groups with no + members are present, and every snapshot carries a `groupEnumeration` field + saying whether that enumeration ran or the membership-derived fallback did + (KEY-WP-0018). A failed enumeration aborts the export rather than writing a + smaller snapshot, and read failures are reported rather than skipped. Users, + groups and memberships are sorted, so an unchanged directory exports + identically. Completeness beyond users, groups and memberships — passwords and + MFA credentials in particular — is still not covered. +- The Keycloak CLI takes a `-clients` config and migrates client registrations: + flows follow the declared grants, and audience, tenant, service subject and + roles are carried as protocol mappers with per-client lifetime, handoff URLs + and the secret *reference* as attributes (KEY-WP-0020). What it cannot carry it + names, in a report separate from the consistency check: secret values, MFA + policy enforcement, passwords and factor enrolment, and subject continuity — + Keycloak mints its own `sub`, so the LLDAP canonical ID survives only as an + attribute. The output is a reviewed artifact, not a proven migration; that + proof needs a live provider swap and is not established. +- Snapshot validation is a limited Go rule set, not full machine-readable schema + enforcement. Raw LDAP attribute keys are checked for descriptor validity, + canonical-mapping shadowing and case-only duplicates (KEY-WP-0021); attribute + values are not validated against a directory schema, and no allow-list of + permitted attribute names exists, since that field carries what the canonical + model does not name. The canonical YAML model and discovery metadata now match the + runtime client-registration surface and are held there by a conformance check + (KEY-WP-0017); the Go model is the runtime authority and the YAML the reviewed + contract. That is narrower than schema enforcement in general. +- The authorization-code grant now binds the redirect URI, + enforces grant-type eligibility, authenticates confidential clients and + consumes codes atomically, and UserInfo enforces algorithm, issuer and + access-token purpose (KEY-WP-0016). Upstream Authelia ID tokens are verified + before any claim is trusted — signature against the provider's published keys, + advertised issuer, own client ID in the audience, validity window — failing + closed and independently of transport (KEY-WP-0019). KeyCape deliberately does + not check or gate on the upstream transport. Enforcing these bindings + is not complete profile conformance. Relying parties must repeat `redirect_uri` + on the token exchange and present the access token, not the ID token, to + `/userinfo`; see [authorization-code bindings](docs/authorization-code-bindings.md). + See the assessment below. +- Tests cover local handlers, adapters, transformations and CLI protocol behavior. + They do not establish complete replacement against a running Keycloak/full-LDAP + stack. The Scenario B/C harnesses now run, and `src/tests/conformance` targets a + live issuer named by `KEYCAPE_TEST_ISSUER`, skipping when it is unset + (KEY-WP-0022). Run against Keycloak 26.0 it verified discovery, the + authorization surface, the published keys and a real token exchange — and + showed that stock Keycloak advertises the excluded `implicit` and `password` + grants, so it is not a drop-in for this profile. Directory migration is proved + end to end into a real OpenLDAP, with every migrated membership resolving, and a + realm built from a live export imports into Keycloak and serves discovery + (KEY-WP-0023). Relying-party behaviour and MFA against a migrated realm remain + unexercised, and credential/MFA migration is not supplied at all, so no harness + can establish it. Subject continuity is explicitly not preserved: the canonical + ID survives as an attribute while Keycloak mints its own `sub`. +- **All ten gaps from the 2026-09-05 assessment are now closed**, G10 last, on + 2026-09-10. Closure means the gap each named no longer holds; it does not mean + nothing is outstanding. The approver registration is published but no human has + logged in through it, the deployed image predates most of this revision, and the + Qonto rotation is deferred by owner decision on evidence. Those are open items + with owners and triggers, which is a different thing from a gap between what + this document claims and what is true. +- **Source behaviour and deployed behaviour are not the same claim, and this + document describes source.** Everything above is what the code in this revision + does, established by 394 Go tests across 23 packages plus the harness + runs named. What has been demonstrated against the live deployment is narrower and is + recorded as committed receipts in `docs/evidence/`: the upstream issuer was + probed and then pinned in `sso/keycape-config` + (`2026-09-09-upstream-issuer-pin.json` — the pre-pin state did **not** match, so + this was a real fix), and both approval service clients were verified live in an + attended owner process, showing JWKS signature verification, exact claims, + excess-scope and wrong-secret denial at a 900s lifetime + (`2026-09-09-keycape-verifier-admission.json`), independently confirmed by the + custody owner. That is live evidence for those two clients specifically — + more than a source claim, less than a general one. On the custody boundary: + client-side retrieval of those secrets is unadmitted and stays so, but the + attended operator path is not a client-side read and never required one; + conflating the two is what left this work recorded as waiting on a run that had + already happened. Those receipts also decline what they did not test: no real + predecessor rotation and no observed wall-clock expiry. Those name a **third + state** this document otherwise has no words for — *implemented and unproven*, + distinct from both missing and done. `verify-client`'s predecessor rejection is + written and unit-tested but has never run against a genuinely distinct + predecessor, so this repository holds a receipt that reads like rotation + evidence and is not. Both owners state that limit independently. Read "there is + a test" and "it has been exercised against the real thing" as separate claims + wherever this document makes one. The deployed image is older than this revision, so the + authorization-code bindings, upstream verification, readiness, graceful + shutdown and the two tenant-related changes are **not yet running anywhere** — + see [operations](docs/operations.md), "Unreleased fail-closed startup changes". + Read `docs/evidence/` before describing any of this as proven live. +- The server listens on HTTP; HTTPS termination is deployment-owned. + `/healthz` is liveness and probes nothing; `/readyz` probes LLDAP, Authelia and + privacyIDEA and gates traffic. `SIGTERM` drains in-flight requests for 15s. + The supported topology is a single replica, since login and authorization state + is process-local — see [operations](docs/operations.md) for that and for the + key-rotation, client-removal and logout limits. Development + Compose needs key and Authelia material absent from the checkout by design; + `scripts/bootstrap-dev.sh` generates it locally and git-ignores it + (KEY-WP-0026). Production + deployment and custody are external; a source implementation or example client + fragment does not prove live registration or consumer cutover. +- Notification receipt closure is done: KEY-WP-0009 is finished, its four handoffs + delivered with receipts readable via `GET /messages/?from_agent=key-cape`. + Approval-client provisioning (KEY-WP-0013-T02) and coordinated Qonto rotation + plus the login-proxy consumer split (KEY-WP-0014-T04) remain open. Both requests + are now delivered and both wait on owners outside this repository — + railiance-platform for custody admission, approval-engine for the exact human + callback, ops-warden for the route contract and a rotation execution/rollback + authority. No client is materialized in the deployed image and no route changed. + +## Deliberate exclusions + +General-purpose IAM, dynamic client registration, implicit/password grants, +wildcard redirects, arbitrary identity brokering, resource authorization policy, +OpenBao credential custody, and Keycloak operations are outside this repository's +ownership. Registration/enrollment handoffs do not implement user provisioning +or factor enrollment themselves. + +## Entry points and verification + +Run the issuer with `keycape --config PATH`; `keycape server` and +`keycape migrate` are not implemented subcommands. Caller commands are +`keycape login`, `keycape service-token` and `keycape verify-client`. +`keycape check-config` validates a rendered configuration offline, before a +restart (see [operations](docs/operations.md)). + +Separate binaries under `src/cmd/` are `validator`, `lldap-export`, +`keycape-to-keycloak`, and `lldap-to-ldap`. `make -C src build` places binaries in +root `bin/` — the root `make build` only compiles and installs nothing. The +container packages all five binaries with the issuer as its entrypoint +(KEY-WP-0026). Tests live under `src/tests/` and alongside packages in +`src/internal/`. + +`make test`, `make lint` (Go vet), `make build`, and `make contract-test` are the +root checks. The capability-contract check requires the sibling NetKingdom +validator. These checks passed for the reviewed revision in the preceding +implementation session and were rerun for this one; `src/tests/conformance` is +additionally runnable against a live issuer via `KEYCAPE_TEST_ISSUER` and skips +when it is unset. The scope assessment is a source review: it establishes what +the code does, not what any deployment is currently running. + +Use this repo for its bounded issuer, caller JWT acquisition, identity adapters +and migration preparation. Consult [INTENT.md](INTENT.md) for the architectural +target, [native authentication](docs/native-authentication.md) for caller +constraints, [operations](docs/operations.md) for the supported topology and the +unreleased fail-closed changes, `docs/evidence/` for what has been proven against +the live deployment, and the [reassessment](history/2026-09-09-scope-reassessment.md) +— with the [2026-09-05 assessment](history/2026-09-05-011726-scope-intent-assessment.md) +it supersedes — for evidence, priorities and remaining gaps. + +## Provided capabilities + +```capability +type: security +title: Bounded OIDC and service-token issuance +description: Implements static-client browser PKCE and client-credentials authentication with RS256 identity claims through Authelia, LLDAP and privacyIDEA; complete Keycloak interchangeability remains unproven. +keywords: [oidc, pkce, authentication, iam, sso, mfa, identity, service-token] +``` + +```capability +type: security +title: Verified caller JWT acquisition +description: Provides native browser login and service-token exchange commands with HTTPS discovery, JWKS verification and private token-file delivery. +keywords: [oidc, pkce, jwt, cli, authentication, jwks] +``` + +```capability +type: security +title: Directory validation and migration artifact generation +description: Checks canonical snapshots and produces LLDAP exports, basic Keycloak realm JSON and target-specific LDIF; does not perform or prove a complete live migration. +keywords: [migration, identity, lldap, keycloak, ldif, validation] +``` diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/docs/operations.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/docs/operations.md new file mode 100644 index 0000000..3092a16 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/docs/operations.md @@ -0,0 +1,283 @@ +# Operating KeyCape + +The supported deployment topology and the limits that come with it. These are +deliberate boundaries of the current implementation, not defects awaiting a fix: +where a limit is a consequence of a design choice, the choice is named. + +## Topology: exactly one replica + +**Run one instance per issuer.** Authorization codes, login sessions and +registration/enrollment handoffs are held in process memory. There is no shared +store, and no sticky-session configuration makes this safe: a browser that starts +a login on one replica and returns from Authelia to another finds no session and +must start again. Two replicas do not halve the failure rate, they roughly double +the login failure rate. + +This is a documented exclusion rather than a missing feature. Adding a shared +session store is a real option if the profile ever requires horizontal scale; it +is not required today. + +Consequences to plan for: + +- **A restart drops in-flight logins.** Anyone mid-login gets an error and must + start again. Issued tokens are unaffected — they are self-contained JWTs and + stay valid until they expire. +- **Rolling deployments are single-instance rollovers**, so expect a brief window + where new logins fail. Draining (below) protects requests already in flight, not + logins waiting on a human at Authelia's password prompt. + +## Liveness and readiness + +| Endpoint | Answers | Use it for | +| --- | --- | --- | +| `/healthz` | Is the process up? Probes nothing. | Liveness. | +| `/readyz` | Are LLDAP, Authelia and privacyIDEA reachable? | Readiness / traffic gating. | + +Keep these distinct. Wiring liveness to `/readyz` means an orchestrator restarts +KeyCape whenever a dependency blinks, turning someone else's blip into an outage +of your own — and a restart also discards every in-flight login, making it worse +than the condition it reacted to. + +`/readyz` returns 200 with `status: ready`, or 503 with `status: not_ready` and +the failing check named. It reports *which* check failed, never *why*: the +endpoint is unauthenticated and upstream error text carries hostnames and +occasionally credentials-in-URLs. The reason is in the server log. + +Probes are reachability and credential checks, not functional tests. LLDAP is +probed with a bind, so a rotated or revoked service password is caught — that +leaves the port open and every lookup failing, which is exactly what readiness +should catch. Authelia and privacyIDEA are probed with a plain HTTP GET: any +response means something is listening and speaking HTTP. + +Results are cached for 2 seconds and each probe is bounded at 3 seconds. The +cache is not an optimisation: the endpoint is necessarily unauthenticated, and +without it anyone able to reach it could drive one upstream request per +dependency per call. + +## Shutdown + +On `SIGTERM` or `SIGINT` the server stops accepting connections and gives +in-flight requests up to 15 seconds to finish. The grace period is deliberately +under the 30-second read/write timeouts, so a stuck request cannot outlive the +window an orchestrator typically allows before `SIGKILL`. + +In-memory login and authorization state is not preserved across shutdown, by +design — see the topology section. + +## Key and registration lifecycle + +The signing key and all client registrations are read once at startup. Both are +changed by editing configuration and restarting; there is no reload signal and no +rotation service. + +**Check a changed configuration before the restart.** KeyCape exits at startup on +a configuration it refuses, and with one replica that stops sign-in for every +client. `keycape check-config --config PATH [--env-names A,B] [--policy-store]` +runs every startup check that needs no secret value, signing key or upstream, and +prints a JSON report with no values in it. The checks cover validation (including +redirect URIs with `?` or `*`, and duplicate `clientId`s), building the client +registry, the global `tokenLifetime`, and the clients the policy store requires +when `--policy-store` is set. With `--env-names`, it also checks that every +`env:` secretRef is among the variables the Deployment provides. It exits non-zero +when the server would refuse to start. It does not check that the key file is +readable or that a secret is non-empty, because it never sees either. + +**The key ID is the constant `key-1`.** Rotating the signing key while keeping +that identifier is a trap: a consumer caching JWKS by `kid` may keep the old key +and reject freshly issued tokens until its cache expires. Plan rotation as +"publish new keys, let consumers refetch, then issue with the new key", and treat +a same-`kid` swap as a breaking change for anyone caching aggressively. + +**Removing a client registration does not revoke tokens already issued to it.** +There is no introspection or revocation endpoint, so an issued token stays valid +until it expires — 15 minutes by default, or the client's `tokenLifetime`. To cut +off a compromised client, remove the registration *and* wait out the lifetime, or +rotate the signing key if you cannot. + +**`/logout` clears the local KeyCape session only.** It does not end the Authelia +session and does not revoke any issued token. A user who logs out and back in may +not be prompted for credentials, because the upstream session is still valid. + +## Transport + +The server speaks plain HTTP. TLS termination belongs to the deployment. KeyCape +deliberately does not check or gate on the transport used to reach its upstream +providers either; upstream ID tokens are verified cryptographically instead, so +that assurance does not depend on the network being what we believe it is +(KEY-WP-0019). + +## Upstream issuer pinning (read before rolling out KEY-WP-0019) + +KeyCape verifies upstream ID tokens against the issuer the provider advertises, +discovered server-side from `authelia.tokenBaseURL`. Some providers — Authelia +among them — derive the advertised issuer from the **request Host**, so the value +KeyCape learns over an in-cluster service address is not the value minted into +tokens issued for the browser-facing host. + +Where that is true, pin it explicitly: + +```yaml +authelia: + tokenBaseURL: "http://authelia.sso.svc.cluster.local:9091" + issuer: "https://auth.coulomb.social" # exactly the iss claim in ID tokens + jwksUrl: "http://authelia.sso.svc.cluster.local:9091/jwks.json" +``` + +Verification fails closed, so a mismatch means **every human login fails** — and +it looks like a broken login rather than a configuration error. Check the +issuer before rolling out, not after. The failure is diagnosable: the +authentication failure event carries `error_type=id_token_issuer_mismatch`, as +distinct from `id_token_signature` or `provider_keys_unavailable`. + +Confirm the value with the Host the provider will actually see: + +```bash +curl -s -H "Host: auth.coulomb.social" \ + http://authelia.sso.svc.cluster.local:9091/.well-known/openid-configuration \ + | jq -r .issuer +``` + +## Before any live change + +The estate-wide rules are in +`the-custodian/docs/agent-environment-orientation.md` (revision 2026-09-21); +read it first. The parts that apply to KeyCape's objects in `sso` and `mfa`: + +- The founder's go-ahead comes first, recorded as + `ADMINISTER @ realm:kubernetes/railiance01`, `activation=APPROVED`. Run changes + as `ssh railiance01 '…'`. A harness block is a stop signal, not something to + route around. +- Check CPU headroom before a restart or rollout + (`kubectl describe node | grep -A4 "Allocated resources"`). A rolling update + starts the new pod before stopping the old one. +- Diff first: client-side `kubectl diff`, then `--dry-run=server`. +- **Never `kubectl apply` a Secret, and never read one's metadata.** `apply` + copies the whole Secret into the `last-applied-configuration` annotation, so + even a metadata read prints the signing key and client secrets. Write Secrets + with `kubectl replace`. To test for the annotation without printing it: + `kubectl get secret -n sso -o go-template='{{ if index .metadata.annotations "kubectl.kubernetes.io/last-applied-configuration" }}HAS-ANNOTATION{{ else }}clean{{ end }}'`. + +- **Never run any other go-template or jsonpath against a Secret.** When a + template fails, for example `len` on an absent field, kubectl prints the whole + raw object as debugging output, `.data` included. The presence check above is + the only tested template. This leaked `sso/keycape-config` into an agent + session on 2026-09-23 (see below). + +**Open finding, 2026-09-23:** `sso/keycape-config`, `sso/authelia-secrets`, +`sso/lldap-secrets` and `mfa/privacyidea-config` all carry that annotation. The +earlier rotation script wrote them with `apply`; it now uses `replace`. Anyone +who can `get` these Secrets could read the data anyway, so this is not a new +reader. What it breaks is the assumption that a metadata read is safe. The fix +is a live change and waits for the founder's go-ahead: +`kubectl annotate secret -n kubectl.kubernetes.io/last-applied-configuration-` +for each of the four. It prints no value and leaves `.data` untouched, so pods +need no restart. Re-check with the template above afterwards. + +**Superseded the same day by an exposure.** While checking these Secrets' owners +before the cleanup, an agent ran an untested template. It failed and printed +`keycape-config` in full into the session transcript: the signing key, the LLDAP +bind password and the Authelia client secret. The other three Secrets were +probably printed as well. All four must be treated as exposed and rotated; the +annotation cleanup folds into that rotation. + +**Rotation deferred (founder decision, 2026-09-24, builder mode).** Tracked as +the-custodian `CUST-WP-0073-T05` with event triggers: the first production +workload or customer data, any sign the transcript left the workstation, or a +planned key rotation. The same workplan removes the problem class by giving +agents a cluster identity that cannot read these objects at all. + +## Current image (2026-10-15) + +The live issuer is +`forgejo.coulomb.social/coulomb/key-cape@sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010`, +source `8c6c071`. Over the previous live source `1164f65` it adds the +duplicate-clientId startup check (`c528daa`), the lost-authenticator help link +(`205a1c0`), and the opt-in sign-in diagnosis page with `uid`/`assurance` on +`auth_success` (`af3b1b0`). Founder-run on 2026-10-15: `keycape check-config` +on the live config was valid (13 clients, no errors), `kubectl diff` showed only +the image, server dry-run passed, Recreate rollout succeeded, pod ready with 0 +restarts and discovery answering 200. The owner manifest is +`net-kingdom/sso-mfa/k8s/keycape/deployment.yaml`. Rollback is +`sha256:6f79a2af1c695d39480173fad013facd84d21e7732860366af519302a2c496b8`. + +## Fail-closed startup changes from source 11ce29a + +These remain in the current image. They first shipped in +`sha256:8dc35801d1d2b0905a43530c40b1fed0107fc179141fe5b2b614c52c01714fc9`, +Forgejo run 69, source `11ce29a`, confirmed running on 2026-09-25. They fail +closed. The 2026-09-24 Vergabe journey completed after one token-exchange +failure; see `docs/evidence/2026-09-24-fresh-login-and-account-switch.md`. + +**1. Browser clients reject service-identity fields (`74b35b6`, KEY-WP-0028).** +Config validation now rejects `serviceSubject` or `roles` on a client whose +grant is not `client_credentials`; those fields were silently ignored there, so a +registration could look effective and do nothing. This is a **startup** error: +a stray field in the deployed config stops the process from booting rather than +degrading. The live `sso/keycape-config` was checked on 2026-09-09 and none of +its three browser clients carries either field, so it passes as deployed. The +custody owner confirmed independently that the config was last written during the +2026-09-09 activation (resource version 58747126) and has not been edited since, +so the inspected state is the state that will boot — and that they will re-check +the browser clients if anything writes that Secret before the window. `tenant` is deliberately not rejected; +see below. + +**2. A conflicting tenant binding refuses issuance (`329e48f`, KEY-WP-0013-T05).** +A client registration may declare a `tenant`, which is supplied when the +directory has placed the user nowhere and enforced when it has. A declared zone +that **conflicts** with a directory assignment refuses the token with `403` and +`error_type: tenant_binding`. No deployed client declares a tenant on a browser +grant today, so nothing changes on rollout; the refusal only becomes reachable +once the approver client is registered. See +[the tenant contract](tenant-claim-contract.md). + +**3. KeyCape enforces login freshness itself (`11ce29a`, KEY-WP-0033).** +`prompt=login` is no longer forwarded to Authelia, because 4.38 refuses it for +every real login. `prompt=login` and `max_age=0` go upstream as `max_age=10`, +and the callback refuses any upstream `auth_time` that misses the requirement, +or is absent (`stale_upstream_authentication`). This changes nothing for clients +that do not ask for freshness. The proof is a completed fresh-login journey, +not a redirect check. + +The live boot is the image above. A further rollout is not required for these +three changes. + +## What is not claimed + +No resource-efficiency or throughput bounds are asserted here. Nothing in this +repository benchmarks KeyCape, so any figure would be invention. Measure it in +your own deployment before sizing against it. + +## Account recovery and browser sign-out + +Set `KEYCAPE_ACCOUNT_PORTAL_URL=https://users.coulomb.social` and +`KEYCAPE_BROWSER_LOGOUT_URL=https://auth.coulomb.social/logout` together to enable +central browser recovery. Both must be HTTPS, with no credentials, query or +fragment. They override the corresponding YAML accountPortalURL/browserLogoutURL. +Failed browser authorization and expired callbacks redirect to the fixed +`/access-recovery` page with no state, code, claimed user or browser return URL. +API authorization/token validation remains fail-closed. + +`GET /account/logout` displays confirmation and creates a short-lived Secure, +HttpOnly host-only CSRF cookie. POST checks that cookie, the form nonce and exact +issuer Origin; only then does it invalidate the current KeyCape session and +redirect to Authelia's browser `/logout`, with a fixed portal `/logged-out` return. +Authelia owns and deletes its session cookie. Its v4.38 SignOut view validates the +return destination and performs the same-origin logout API call. Reference: +https://github.com/authelia/authelia/blob/v4.38.19/web/src/views/LoginPortal/SignOut/SignOut.tsx +Existing RP sessions and issued JWTs are not revoked. The original `/logout` +endpoint retains its local-only contract. Actual shared-session destruction +requires browser execution; a redirect-only smoke is not acceptance evidence. + +## Sign-in diagnosis page (NK-WP-0051-T02) + +A client with `signInDiagnosis: true` in its registration shows a page before +the redirect back to it: `uid`, `groups`, `assurance` and `client_id` as defined +in `net-kingdom/docs/sign-in-diagnosis.md`, with one continue link carrying the +unchanged code. Default is off. The repo enables it only for `openbao-admin` in +`config/dev-config.yaml`; the live flag is set on the OpenBao browser client +(`openbao-admin`) in `config.yaml` of the `sso/keycape-config` Secret, and takes +effect on the next issuer restart. It is live since 2026-10-15 (rv 75372162), +set with net-kingdom `sso-mfa/k8s/keycape/openbao-signin-diagnosis-rollout.py`; +`--operation remove` plus a restart is the rollback. A redirect URI that is not http(s) +skips the page. Every `auth_success` log line now carries `client_id`, `uid` and +`assurance`, and no token, code, email or one-time password. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/cmd/keycape/main.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/cmd/keycape/main.go new file mode 100644 index 0000000..16de34c --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/cmd/keycape/main.go @@ -0,0 +1,465 @@ +// keycape is the main server binary for the KeyCape IAM profile service. +// It orchestrates Authelia, LLDAP, and privacyIDEA to implement the +// NetKingdom IAM Profile (OIDC/PKCE Authorization Code Flow). +package main + +import ( + "context" + "crypto/rsa" + "crypto/x509" + "encoding/json" + "encoding/pem" + "flag" + "fmt" + "keycape/internal/server/readiness" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/rs/zerolog" + + "keycape/internal/adapters/authelia" + "keycape/internal/adapters/lldap" + "keycape/internal/adapters/privacyidea" + "keycape/internal/adapters/tenantengine" + "keycape/internal/authclient" + "keycape/internal/config" + "keycape/internal/domain" + servererrors "keycape/internal/server/errors" + "keycape/internal/server/oidc" + "keycape/internal/server/policy" + "keycape/internal/server/telemetry" +) + +const version = "0.1.0" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "check-config" { + os.Exit(runCheckConfig(os.Args[2:], os.Stdout)) + } + + if len(os.Args) > 1 && os.Args[1] == "probe-upstream-issuer" { + if err := authelia.RunIssuerProbe(context.Background(), os.Args[2:], os.Stdout); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + if len(os.Args) > 1 && (os.Args[1] == "login" || os.Args[1] == "service-token" || os.Args[1] == "verify-client") { + if err := authclient.Run(context.Background(), os.Args[1:], os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return + } + + log := zerolog.New(os.Stdout).With().Timestamp().Logger() + + // ----------------------------------------------------------------- + // 1. Parse flags and load config. + // ----------------------------------------------------------------- + var cfgPath string + flag.StringVar(&cfgPath, "config", "", "path to YAML config file (env: KEYCAPE_CONFIG)") + flag.Parse() + + cfg, err := config.Load(cfgPath) + if err != nil { + log.Error().Err(err).Msg("failed to load config") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 2. Validate config. + // ----------------------------------------------------------------- + errs := config.ValidateConfig(cfg) + if len(errs) > 0 { + log.Error().Strs("errors", errs).Msg("config validation failed") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 3. Load RSA private key. + // ----------------------------------------------------------------- + privateKey, err := loadPrivateKey(cfg.PrivateKeyPEM) + if err != nil { + log.Error().Err(err).Str("path", cfg.PrivateKeyPEM).Msg("failed to load private key") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 4. Build JWKS from public key. + // ----------------------------------------------------------------- + ks := oidc.NewKeySet() + ks.AddKey("key-1", &privateKey.PublicKey) + + // ----------------------------------------------------------------- + // 5. Build client registry. + // ----------------------------------------------------------------- + clients, err := buildClientRegistry(cfg.Clients) + if err != nil { + log.Error().Err(err).Msg("failed to build client registry") + os.Exit(1) + } + + // ----------------------------------------------------------------- + // 6. Create adapters. + // ----------------------------------------------------------------- + lldapAdapter := lldap.New(cfg.LLDAP) + autheliaAdapter := authelia.New(cfg.Authelia, nil) + privacyIDEAAdapter := privacyidea.New(cfg.PrivacyIDEA, nil) + + // ----------------------------------------------------------------- + // 7. Create telemetry emitter. + // ----------------------------------------------------------------- + emitter := telemetry.NewLogEmitter(log) + + // ----------------------------------------------------------------- + // 8. Create enforcement registry. + // ----------------------------------------------------------------- + enforcement := servererrors.DefaultRegistry() + + // ----------------------------------------------------------------- + // 9. Create session store. + // ----------------------------------------------------------------- + sessions := oidc.NewSessionStore() + + // ----------------------------------------------------------------- + // 10. Parse token lifetime. + // ----------------------------------------------------------------- + tokenLifetime := 15 * time.Minute + if cfg.TokenLifetime != "" { + d, err := time.ParseDuration(cfg.TokenLifetime) + if err != nil { + log.Error().Err(err).Str("tokenLifetime", cfg.TokenLifetime).Msg("invalid tokenLifetime") + os.Exit(1) + } + tokenLifetime = d + } + + // ----------------------------------------------------------------- + // 11. Build issuer base URL. + // ----------------------------------------------------------------- + issuer := strings.TrimRight(cfg.Issuer, "/") + + // ----------------------------------------------------------------- + // 12. Register HTTP handlers. + // ----------------------------------------------------------------- + mux := http.NewServeMux() + + // Discovery. + mux.Handle("/.well-known/openid-configuration", oidc.NewDiscoveryHandler(oidc.DiscoveryConfig{ + Issuer: issuer, + AuthorizationEndpoint: issuer + "/authorize", + TokenEndpoint: issuer + "/token", + JWKSUri: issuer + "/jwks", + UserinfoEndpoint: issuer + "/userinfo", + EndSessionEndpoint: issuer + "/logout", + Clients: clients, + })) + + // JWKS. + mux.Handle("/jwks", oidc.NewJWKSHandler(ks)) + + // Authorize handler (with enforcement middleware). + logins := oidc.NewLoginSessionStore() + authorizeHandler := &oidc.AuthorizeHandler{ + AccountPortalURL: cfg.AccountPortalURL, + ClientConfig: clients, + Auth: autheliaAdapter, + MFA: privacyIDEAAdapter, + Sessions: sessions, + Logins: logins, + Handoffs: oidc.NewHandoffStore(), + Issuer: issuer, + Emitter: emitter, + Users: lldapAdapter, + } + if path := os.Getenv("KEYCAPE_POLICY_PATH"); path != "" { + policies, err := policy.Open(path, clients) + if err != nil { + log.Error().Msg("authentication policy store unavailable") + os.Exit(1) + } + authorizeHandler.EffectivePolicy = policies.Effective + mux.Handle("/platform/authentication-policy", policy.Handler(policies, issuer, &privateKey.PublicKey)) + } + mux.Handle("/authorize", enforcement.Middleware(authorizeHandler)) + mux.Handle("/authorize/callback", authorizeHandler) + mux.Handle("/authorize/return", authorizeHandler) + mux.Handle("/authorize/register", authorizeHandler) + mux.Handle("/logout", &oidc.LogoutHandler{ + ClientConfig: clients, + Logins: logins, + SecureCookie: strings.HasPrefix(strings.ToLower(issuer), "https://"), + }) + + if cfg.AccountPortalURL != "" { + mux.Handle("/account/logout", &oidc.AccountLogoutHandler{ + PortalURL: cfg.AccountPortalURL, UpstreamLogoutURL: cfg.BrowserLogoutURL, + Logins: logins, Issuer: issuer, + }) + } + + // Token handler (with enforcement middleware). + tokenHandler := &oidc.TokenHandler{ + ClientConfig: clients, + Sessions: sessions, + Users: lldapAdapter, + SigningKey: privateKey, + Issuer: issuer, + TokenLifetime: tokenLifetime, + Emitter: emitter, + // Opt-in: nil unless tenantEngine.baseURL is configured, which is what + // leaves tenant_roles off the stock server (KEY-WP-0024). The adapter + // fails open, so a configured-but-unreachable cache omits the claim + // rather than failing issuance. + TenantEngine: buildTenantEngine(cfg.TenantEngine), + } + mux.Handle("/token", enforcement.Middleware(tokenHandler)) + + // Userinfo handler. + mux.Handle("/userinfo", &oidc.UserinfoHandler{ + Users: lldapAdapter, + SigningKey: &privateKey.PublicKey, + Issuer: issuer, + Emitter: emitter, + }) + + // Healthz. + // /readyz gates traffic on the dependencies a login needs; /healthz stays a + // liveness signal (KEY-WP-0025). Restarting KeyCape because LLDAP blinked + // would convert a dependency blip into an outage of our own, so the two must + // not be the same endpoint. + mux.Handle("/readyz", readiness.New(2*time.Second, 3*time.Second, + readiness.Check{Name: "lldap", Probe: func(ctx context.Context) error { + return lldapAdapter.Ping(ctx) + }}, + readiness.Check{Name: "authelia", Probe: httpReachable(cfg.Authelia.TokenBaseURL, cfg.Authelia.BaseURL)}, + readiness.Check{Name: "privacyidea", Probe: httpReachable(cfg.PrivacyIDEA.BaseURL)}, + readiness.Check{Name: "factor_reader", Probe: privacyIDEAAdapter.Probe}, + )) + + // Liveness only: this says the process is up, and deliberately probes + // nothing. See /readyz. + mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(map[string]string{ + "status": "ok", + "version": version, + }) + }) + + // Inject emitter into request context. + handler := withEmitter(mux, emitter) + + // ----------------------------------------------------------------- + // 13. Start HTTP server. + // ----------------------------------------------------------------- + addr := fmt.Sprintf(":%d", cfg.Port) + if cfg.Port == 0 { + addr = ":8080" + } + + log.Info(). + Str("issuer", issuer). + Str("addr", addr). + Str("environment", cfg.Environment). + Str("version", version). + Msg("starting keycape server") + + srv := &http.Server{ + Addr: addr, + Handler: handler, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + + // Serve in the background so shutdown can be driven by a signal. Without + // this the process died on SIGTERM mid-request, so every rolling restart + // returned errors to whoever was logging in at that moment. + serveErr := make(chan error, 1) + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + serveErr <- err + return + } + serveErr <- nil + }() + + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + + select { + case err := <-serveErr: + if err != nil { + log.Error().Err(err).Msg("server error") + os.Exit(1) + } + case sig := <-signals: + log.Info().Str("signal", sig.String()).Msg("shutting down") + // In-flight requests get a bounded window to finish. In-memory login and + // authorization state does not survive this and is not meant to: a + // browser mid-login must start again after a restart. That is a + // documented limit of the single-replica topology, not a bug to paper + // over here -- see docs/operations.md. + ctx, cancel := context.WithTimeout(context.Background(), shutdownGrace) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Error().Err(err).Msg("graceful shutdown failed; exiting anyway") + os.Exit(1) + } + log.Info().Msg("shutdown complete") + } +} + +// shutdownGrace bounds how long in-flight requests may finish after a signal. +// Kept under the 30s read/write timeouts so a stuck request cannot outlive the +// window an orchestrator typically allows before sending SIGKILL. +const shutdownGrace = 15 * time.Second + +// httpReachable probes the first non-empty URL with a GET, reporting whether the +// dependency answered at all. It is a reachability check, not a functional one: +// any HTTP response means something is listening and speaking HTTP there. +func httpReachable(candidates ...string) func(context.Context) error { + target := "" + for _, candidate := range candidates { + if candidate != "" { + target = candidate + break + } + } + return func(ctx context.Context) error { + if target == "" { + return fmt.Errorf("no endpoint configured") + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return nil + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +// loadPrivateKey reads a PEM file and parses the RSA private key. +// Supports both PKCS#1 ("RSA PRIVATE KEY") and PKCS#8 ("PRIVATE KEY") PEM blocks. +func loadPrivateKey(path string) (*rsa.PrivateKey, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read key file: %w", err) + } + + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("no PEM block found in %q", path) + } + + switch block.Type { + case "RSA PRIVATE KEY": + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse PKCS1 private key: %w", err) + } + return key, nil + case "PRIVATE KEY": + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse PKCS8 private key: %w", err) + } + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("private key is not an RSA key") + } + return rsaKey, nil + default: + return nil, fmt.Errorf("unexpected PEM block type %q; expected RSA PRIVATE KEY or PRIVATE KEY", block.Type) + } +} + +// buildClientRegistry converts []ClientConfig into the map used by handlers. +func buildClientRegistry(cfgClients []config.ClientConfig) (map[string]*domain.Client, error) { + return buildClientRegistryWith(cfgClients, os.Getenv) +} + +// buildClientRegistryWith resolves env: secretRefs through getenv, so +// check-config can build the registry without any secret value. +func buildClientRegistryWith(cfgClients []config.ClientConfig, getenv func(string) string) (map[string]*domain.Client, error) { + m := make(map[string]*domain.Client, len(cfgClients)) + for i := range cfgClients { + c := &cfgClients[i] + clientSecret := "" + var clientTokenLifetime time.Duration + if strings.HasPrefix(c.SecretRef, "env:") { + envName := strings.TrimPrefix(c.SecretRef, "env:") + clientSecret = getenv(envName) + if clientSecret == "" { + return nil, fmt.Errorf("client %q secret environment variable %q is empty", c.ClientID, envName) + } + } + if c.TokenLifetime != "" { + parsedLifetime, parseErr := time.ParseDuration(c.TokenLifetime) + if parseErr != nil { + return nil, fmt.Errorf("client %q tokenLifetime is invalid: %w", c.ClientID, parseErr) + } + clientTokenLifetime = parsedLifetime + } + m[c.ClientID] = &domain.Client{ + ClientID: c.ClientID, + DisplayName: c.DisplayName, + RedirectURIs: c.RedirectURIs, + AllowedScopes: c.AllowedScopes, + GrantTypes: c.GrantTypes, + ClientType: c.ClientType, + SecretRef: c.SecretRef, + ClientSecret: clientSecret, + Audience: c.Audience, + ServiceSubject: c.ServiceSubject, + Tenant: c.Tenant, + Roles: c.Roles, + TokenLifetime: clientTokenLifetime, + MFARequired: c.MFARequired, + MFAOptional: c.MFAOptional, + RegistrationURL: c.RegistrationURL, + EnrollmentURL: c.EnrollmentURL, + SignInDiagnosis: c.SignInDiagnosis, + } + } + return m, nil +} + +// withEmitter wraps a handler to inject the telemetry emitter into every request context. +func withEmitter(next http.Handler, e telemetry.Emitter) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := telemetry.WithEmitter(context.Background(), e) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// buildTenantEngine returns the tenant_roles cache client, or nil when the +// claim is not configured. Validation has already accepted the URL and timeout. +func buildTenantEngine(cfg config.TenantEngineConfig) *tenantengine.Client { + if cfg.BaseURL == "" { + return nil + } + var httpClient *http.Client + if cfg.Timeout != "" { + if timeout, err := time.ParseDuration(cfg.Timeout); err == nil { + httpClient = &http.Client{Timeout: timeout} + } + } + return tenantengine.New(cfg.BaseURL, httpClient) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/config.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/config.go new file mode 100644 index 0000000..7895b73 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/config.go @@ -0,0 +1,144 @@ +// Package config handles loading and validating the KeyCape server configuration +// from a YAML file. The config path is resolved from the --config flag or the +// KEYCAPE_CONFIG environment variable. +package config + +import ( + "fmt" + "os" + "time" + + "gopkg.in/yaml.v3" + + "keycape/internal/adapters/authelia" + "keycape/internal/adapters/lldap" + "keycape/internal/adapters/privacyidea" + "keycape/internal/domain" +) + +// Config is the top-level server configuration. +type Config struct { + AccountPortalURL string `yaml:"accountPortalURL,omitempty"` + BrowserLogoutURL string `yaml:"browserLogoutURL,omitempty"` + Issuer string `yaml:"issuer"` + Port int `yaml:"port"` + TokenLifetime string `yaml:"tokenLifetime"` + PrivateKeyPEM string `yaml:"privateKeyPem"` + LLDAP lldap.Config `yaml:"lldap"` + Authelia authelia.Config `yaml:"authelia"` + PrivacyIDEA privacyidea.Config `yaml:"privacyidea"` + Clients []ClientConfig `yaml:"clients"` + Environment string `yaml:"environment"` + TenantEngine TenantEngineConfig `yaml:"tenantEngine,omitempty"` +} + +// TenantEngineConfig configures the optional tenant_roles cache claim. +// +// Opt-in by design: an empty baseURL leaves the claim off entirely, which is +// what the stock server does. tenant_roles is a cache callers must not trust for +// privileged decisions, and the adapter fails open, so enabling it is a +// performance choice rather than a security one (KEY-WP-0024). +type TenantEngineConfig struct { + // BaseURL is tenant-engine's cache-read endpoint. Empty disables the claim. + BaseURL string `yaml:"baseURL,omitempty"` + + // Timeout bounds the lookup, which sits on the synchronous token-issuance + // path. Empty uses the adapter's own short default. + Timeout string `yaml:"timeout,omitempty"` +} + +// ClientConfig is a static OIDC client registration. +type ClientConfig struct { + ClientID string `yaml:"clientId"` + Audience string `yaml:"audience,omitempty"` + DisplayName string `yaml:"displayName"` + RedirectURIs []string `yaml:"redirectUris"` + AllowedScopes []string `yaml:"allowedScopes"` + GrantTypes []string `yaml:"grantTypes"` + ClientType string `yaml:"clientType"` // "confidential" | "public" + SecretRef string `yaml:"secretRef,omitempty"` + ServiceSubject string `yaml:"serviceSubject,omitempty"` + Tenant string `yaml:"tenant,omitempty"` + Roles []string `yaml:"roles,omitempty"` + TokenLifetime string `yaml:"tokenLifetime,omitempty"` + MFAOptional bool `yaml:"mfaOptional,omitempty"` + MFARequired *bool `yaml:"mfaRequired,omitempty"` + RegistrationURL string `yaml:"registrationUrl,omitempty"` + EnrollmentURL string `yaml:"enrollmentUrl,omitempty"` + // SignInDiagnosis opts the client into the pre-redirect sign-in + // diagnosis page (NK-WP-0051-T02). Default off. + SignInDiagnosis bool `yaml:"signInDiagnosis,omitempty"` +} + +// Load reads and parses the YAML config file at path. +// If path is empty, it falls back to the KEYCAPE_CONFIG environment variable. +// Returns an error if the file cannot be read or parsed. +func Load(path string) (*Config, error) { + if path == "" { + path = os.Getenv("KEYCAPE_CONFIG") + } + if path == "" { + return nil, fmt.Errorf("config: no config path specified (use --config or KEYCAPE_CONFIG)") + } + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("config: read %q: %w", path, err) + } + + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, fmt.Errorf("config: parse %q: %w", path, err) + } + + if value := os.Getenv("KEYCAPE_ACCOUNT_PORTAL_URL"); value != "" { + cfg.AccountPortalURL = value + } + if value := os.Getenv("KEYCAPE_BROWSER_LOGOUT_URL"); value != "" { + cfg.BrowserLogoutURL = value + } + return &cfg, nil +} + +// Registrations converts the configured clients to domain registrations without +// resolving any secret. Migration and inspection tooling needs the registration +// contract — grants, audience, tenant, roles, lifetime, MFA and handoff policy — +// but must never load secret material, so ClientSecret is deliberately left +// empty here. The server has its own conversion that does resolve secrets. +// +// An unparseable tokenLifetime is an error rather than a silent zero: a +// migration that quietly drops a per-client lifetime is the class of defect this +// exists to avoid. +func (c *Config) Registrations() ([]domain.Client, error) { + clients := make([]domain.Client, 0, len(c.Clients)) + for _, cc := range c.Clients { + var lifetime time.Duration + if cc.TokenLifetime != "" { + parsed, err := time.ParseDuration(cc.TokenLifetime) + if err != nil { + return nil, fmt.Errorf("config: client %q tokenLifetime is invalid: %w", cc.ClientID, err) + } + lifetime = parsed + } + clients = append(clients, domain.Client{ + ClientID: cc.ClientID, + DisplayName: cc.DisplayName, + RedirectURIs: cc.RedirectURIs, + AllowedScopes: cc.AllowedScopes, + GrantTypes: cc.GrantTypes, + ClientType: cc.ClientType, + SecretRef: cc.SecretRef, + Audience: cc.Audience, + ServiceSubject: cc.ServiceSubject, + Tenant: cc.Tenant, + Roles: cc.Roles, + TokenLifetime: lifetime, + MFARequired: cc.MFARequired, + MFAOptional: cc.MFAOptional, + RegistrationURL: cc.RegistrationURL, + EnrollmentURL: cc.EnrollmentURL, + SignInDiagnosis: cc.SignInDiagnosis, + }) + } + return clients, nil +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/validate.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/validate.go new file mode 100644 index 0000000..c020e40 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/config/validate.go @@ -0,0 +1,188 @@ +package config + +import ( + "fmt" + "net/url" + "strings" + "time" +) + +// ValidateConfig validates a loaded Config and returns a list of human-readable +// error messages. An empty slice means the config is valid. +// Called at startup — the server must exit 1 if any errors are returned. +func ValidateConfig(cfg *Config) []string { + var errs []string + for name, raw := range map[string]string{"accountPortalURL": cfg.AccountPortalURL, "browserLogoutURL": cfg.BrowserLogoutURL} { + if raw == "" { + continue + } + u, err := url.Parse(raw) + if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" { + errs = append(errs, name+": must be an HTTPS URL without credentials, query or fragment") + } + } + if (cfg.AccountPortalURL == "") != (cfg.BrowserLogoutURL == "") { + errs = append(errs, "accountPortalURL and browserLogoutURL must be configured together") + } + + // Issuer must be a valid URL with an http(s) scheme. + if cfg.Issuer == "" { + errs = append(errs, "issuer: must not be empty") + } else { + u, err := url.Parse(cfg.Issuer) + if err != nil || u.Scheme == "" || u.Host == "" { + errs = append(errs, fmt.Sprintf("issuer: %q is not a valid URL (must include scheme and host)", cfg.Issuer)) + } else if u.Scheme != "http" && u.Scheme != "https" { + errs = append(errs, fmt.Sprintf("issuer: scheme must be http or https, got %q", u.Scheme)) + } + } + + // Port must be in the valid TCP range. + if cfg.Port < 1 || cfg.Port > 65535 { + errs = append(errs, fmt.Sprintf("port: must be between 1 and 65535, got %d", cfg.Port)) + } + + // tenant_roles is opt-in: an empty baseURL disables it. A configured one + // must be usable, since a misconfigured cache source on the token path is + // worse than no cache source (KEY-WP-0024). + if cfg.TenantEngine.BaseURL != "" { + u, err := url.Parse(cfg.TenantEngine.BaseURL) + if err != nil || u.Scheme == "" || u.Host == "" { + errs = append(errs, fmt.Sprintf("tenantEngine.baseURL: %q is not a valid URL (must include scheme and host)", cfg.TenantEngine.BaseURL)) + } else if u.Scheme != "http" && u.Scheme != "https" { + errs = append(errs, fmt.Sprintf("tenantEngine.baseURL: scheme must be http or https, got %q", u.Scheme)) + } + } + if cfg.TenantEngine.Timeout != "" { + if cfg.TenantEngine.BaseURL == "" { + errs = append(errs, "tenantEngine.timeout: set without a baseURL, so tenant_roles stays disabled") + } else if timeout, err := time.ParseDuration(cfg.TenantEngine.Timeout); err != nil { + errs = append(errs, "tenantEngine.timeout: must be a valid duration") + } else if timeout <= 0 || timeout > 10*time.Second { + errs = append(errs, "tenantEngine.timeout: must be greater than 0 and at most 10s; it sits on the token-issuance path") + } + } + + // At least one client must be registered. + if len(cfg.Clients) == 0 { + errs = append(errs, "clients: at least one client must be defined") + } + + // The registry is keyed by clientId, so a duplicate would silently replace + // the earlier registration instead of failing. + seen := map[string]bool{} + + // Each client must have at least one redirect URI and a non-empty clientId. + for i, c := range cfg.Clients { + prefix := fmt.Sprintf("clients[%d] (%s)", i, c.ClientID) + if c.ClientID == "" { + prefix = fmt.Sprintf("clients[%d]", i) + errs = append(errs, prefix+": clientId must not be empty") + } else if seen[c.ClientID] { + errs = append(errs, prefix+": clientId is registered more than once") + } + seen[c.ClientID] = true + if strings.TrimSpace(c.Audience) != c.Audience || strings.ContainsAny(c.Audience, " \t\r\n") { + errs = append(errs, prefix+": audience must be a single non-whitespace identifier") + } + if c.MFAOptional && c.MFARequired != nil { + errs = append(errs, prefix+": mfaOptional cannot be combined with mfaRequired") + } + if c.MFAOptional && contains(c.GrantTypes, "client_credentials") { + errs = append(errs, prefix+": mfaOptional is only supported for browser clients") + } + hasAuthorizationCode := contains(c.GrantTypes, "authorization_code") + hasClientCredentials := contains(c.GrantTypes, "client_credentials") + if (hasAuthorizationCode || !hasClientCredentials) && len(c.RedirectURIs) == 0 { + errs = append(errs, prefix+": redirect_uri: at least one redirectUri must be registered") + } + if hasClientCredentials { + if c.ClientType != "confidential" { + errs = append(errs, prefix+": client_credentials requires clientType confidential") + } + if !strings.HasPrefix(c.SecretRef, "env:") { + errs = append(errs, prefix+": client_credentials requires an env: secretRef") + } + if c.ServiceSubject == "" || c.Tenant == "" { + errs = append(errs, prefix+": client_credentials requires serviceSubject and tenant") + } + if c.TokenLifetime != "" { + lifetime, err := time.ParseDuration(c.TokenLifetime) + if err != nil { + errs = append(errs, prefix+": tokenLifetime must be a valid duration") + } else if lifetime < time.Minute || lifetime > time.Hour { + errs = append(errs, prefix+": tokenLifetime must be between 1m and 1h") + } + } + } else { + if c.TokenLifetime != "" { + errs = append(errs, prefix+": tokenLifetime is only supported for client_credentials clients") + } + // serviceSubject and roles are read only on the client_credentials + // path, so on a browser client they are silently ignored: the + // subject and roles come from the directory user. Rejecting them + // turns a registration that looks effective into a startup error + // (KEY-WP-0028). + // + // tenant is deliberately NOT in this list. A browser client may + // declare one, and humanTenant resolves it against the directory -- + // see docs/tenant-claim-contract.md, "How a human token's tenant is + // resolved". + if c.ServiceSubject != "" { + errs = append(errs, prefix+": serviceSubject is only read for client_credentials clients; a browser client's subject is the directory user") + } + if len(c.Roles) > 0 { + errs = append(errs, prefix+": roles is only read for client_credentials clients; a browser client's roles come from the directory user") + } + } + // Warn about wildcard redirect URIs (they are blocked at runtime anyway). + for _, uri := range c.RedirectURIs { + if strings.ContainsAny(uri, "*?") { + errs = append(errs, prefix+fmt.Sprintf(": redirect_uri %q must not contain wildcards", uri)) + } + } + if c.RegistrationURL != "" { + if err := validateHandoffURL(c.RegistrationURL); err != nil { + errs = append(errs, prefix+": registrationUrl: "+err.Error()) + } + } + if c.EnrollmentURL != "" { + if err := validateHandoffURL(c.EnrollmentURL); err != nil { + errs = append(errs, prefix+": enrollmentUrl: "+err.Error()) + } + } + } + + // Private key PEM path must be provided (existence is checked at startup). + if cfg.PrivateKeyPEM == "" { + errs = append(errs, "privateKeyPem: path must not be empty") + } + + return errs +} + +func contains(values []string, wanted string) bool { + for _, value := range values { + if value == wanted { + return true + } + } + return false +} + +func validateHandoffURL(raw string) error { + u, err := url.Parse(raw) + if err != nil || u.Scheme == "" || u.Host == "" { + return fmt.Errorf("%q is not an absolute URL", raw) + } + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("%q scheme must be http or https", raw) + } + if u.User != nil { + return fmt.Errorf("%q must not contain userinfo", raw) + } + if strings.ContainsAny(u.Host, "*?") || strings.ContainsAny(u.Path, "*") { + return fmt.Errorf("%q must not contain wildcards", raw) + } + return nil +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/discovery.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/discovery.go new file mode 100644 index 0000000..4b0074b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/discovery.go @@ -0,0 +1,127 @@ +// Package oidc implements OIDC profile endpoints for KeyCape. +// Only profile-supported features are advertised — no implicit flow, +// no dynamic registration, no request objects. +package oidc + +import ( + "encoding/json" + "net/http" + "sort" + + "keycape/internal/domain" +) + +// DiscoveryConfig holds the issuer and endpoint URLs for the discovery document. +// UserinfoEndpoint is optional; if empty it is omitted from the document. +type DiscoveryConfig struct { + Issuer string // e.g. "https://auth.netkingdom.local" + AuthorizationEndpoint string + TokenEndpoint string + JWKSUri string + UserinfoEndpoint string // optional, empty = not advertised + EndSessionEndpoint string // optional, empty = not advertised + // Clients supplies the registered clients so scopes_supported reflects the + // scopes this deployment can actually grant, rather than a fixed list that + // omits every configured resource scope (KEY-WP-0017-T03). Nil or empty + // leaves only the baseline OIDC scopes advertised. + Clients map[string]*domain.Client +} + +// discoveryDocument is the JSON shape of /.well-known/openid-configuration. +// Fields are ordered to match common OIDC implementations for readability. +// registration_endpoint is intentionally absent — no dynamic client registration. +type discoveryDocument struct { + Issuer string `json:"issuer"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + JWKSUri string `json:"jwks_uri"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` + EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` + ResponseTypesSupported []string `json:"response_types_supported"` + GrantTypesSupported []string `json:"grant_types_supported"` + CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` + IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` + ScopesSupported []string `json:"scopes_supported"` + TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"` + ClaimsSupported []string `json:"claims_supported"` + SubjectTypesSupported []string `json:"subject_types_supported"` + RequestParameterSupported bool `json:"request_parameter_supported"` + ClaimsParameterSupported bool `json:"claims_parameter_supported"` +} + +// discoveryHandler implements http.Handler for GET /.well-known/openid-configuration. +type discoveryHandler struct { + doc []byte +} + +// NewDiscoveryHandler returns an http.Handler that serves the OIDC discovery document. +// The document is pre-serialised at construction time so every request is a cheap copy. +func NewDiscoveryHandler(cfg DiscoveryConfig) http.Handler { + d := discoveryDocument{ + Issuer: cfg.Issuer, + AuthorizationEndpoint: cfg.AuthorizationEndpoint, + TokenEndpoint: cfg.TokenEndpoint, + JWKSUri: cfg.JWKSUri, + UserinfoEndpoint: cfg.UserinfoEndpoint, + EndSessionEndpoint: cfg.EndSessionEndpoint, + + // Profile-locked values — not negotiable. + ResponseTypesSupported: []string{"code"}, + GrantTypesSupported: []string{"authorization_code", "client_credentials"}, + CodeChallengeMethodsSupported: []string{"S256"}, + IDTokenSigningAlgValuesSupported: []string{"RS256"}, + ScopesSupported: supportedScopes(cfg.Clients), + TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "none"}, + // Core profile claims are emitted on every token, so omitting one here + // is a contract defect rather than missing optional metadata. The + // scope-gated human claims and the optional cached tenant_roles claim + // follow them. + ClaimsSupported: []string{ + "sub", "iss", "aud", "exp", "iat", + "tenant", "tenant_source", "principal_type", "roles", "groups", "assurance", "scope", + "nonce", "preferred_username", "email", "name", "tenant_roles", + }, + SubjectTypesSupported: []string{"public"}, + RequestParameterSupported: false, + ClaimsParameterSupported: false, + } + + b, err := json.Marshal(d) + if err != nil { + // This can only fail if the struct contains un-marshallable types, which it does not. + panic("oidc: failed to marshal discovery document: " + err.Error()) + } + return &discoveryHandler{doc: b} +} + +func (h *discoveryHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "max-age=3600") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(h.doc) +} + +// supportedScopes returns the baseline OIDC scopes plus every scope any +// registered client is allowed to request, sorted for a stable document. The +// baseline is advertised even with no clients configured, since it describes the +// profile rather than a particular deployment. +func supportedScopes(clients map[string]*domain.Client) []string { + seen := map[string]bool{} + scopes := []string{} + for _, scope := range []string{"openid", "profile", "email", "groups"} { + seen[scope] = true + scopes = append(scopes, scope) + } + extra := []string{} + for _, client := range clients { + for _, scope := range client.AllowedScopes { + if scope == "" || seen[scope] { + continue + } + seen[scope] = true + extra = append(extra, scope) + } + } + sort.Strings(extra) + return append(scopes, extra...) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/token.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/token.go new file mode 100644 index 0000000..b1d7f57 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/token.go @@ -0,0 +1,594 @@ +package oidc + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "keycape/internal/adapters/tenantengine" + "keycape/internal/domain" + profileerrors "keycape/internal/errors" + "keycape/internal/server/telemetry" +) + +// TokenHandler implements POST /token. +type TokenHandler struct { + ClientConfig map[string]*domain.Client + Sessions *SessionStore + Users domain.UserRepository + SigningKey *rsa.PrivateKey + Issuer string + TokenLifetime time.Duration + Emitter telemetry.Emitter + // TenantEngine sources the optional tenant_roles claim (KEY-WP-0005-T02). + // Nil disables it entirely -- token issuance never depends on it. + TenantEngine *tenantengine.Client +} + +// tokenResponse is the JSON body returned on a successful token exchange. +type tokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + IDToken string `json:"id_token,omitempty"` +} + +// ServeHTTP handles POST /token. +func (h *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + if err := r.ParseForm(); err != nil { + http.Error(w, "invalid form body", http.StatusBadRequest) + return + } + + grantType := r.FormValue("grant_type") + if grantType == "client_credentials" { + h.serveClientCredentials(w, r) + return + } + clientID := r.FormValue("client_id") + code := r.FormValue("code") + codeVerifier := r.FormValue("code_verifier") + + // 1. Validate grant_type. + if grantType != "authorization_code" { + profileerrors.FeatureNotSupported( + "only grant_type=authorization_code is supported", + "grant_type="+grantType, + ).Write(w, http.StatusBadRequest) + return + } + + // 2. Validate client exists and may use this grant. + client, ok := h.ClientConfig[clientID] + if !ok { + profileerrors.InvalidProfileUsage("unknown client_id", "client_id"). + Write(w, http.StatusBadRequest) + return + } + + // Grant-type eligibility, enforced equivalently to the service path + // (KEY-WP-0016-T02). An empty grantTypes is an implicit authorization-code + // client, matching config validation; a client_credentials-only client must + // not reach the browser path. + if len(client.GrantTypes) > 0 && !containsString(client.GrantTypes, "authorization_code") { + profileerrors.InvalidProfileUsage( + "client is not registered for grant_type=authorization_code", + "grant_type", + ).Write(w, http.StatusBadRequest) + return + } + + // Confidential authorization-code clients authenticate with their secret, + // using the same credential sources as the service grant. A public client + // must not be able to present a secret and be treated as authenticated. + if client.ClientType == "confidential" { + presentedID, secret, ok := basicClientCredentials(r) + if !ok || presentedID != clientID || client.ClientSecret == "" || + !secretsEqual(secret, client.ClientSecret) { + profileerrors.InvalidProfileUsage( + "client authentication failed", + "Authorization", + ).Write(w, http.StatusUnauthorized) + return + } + } + + // 3. Consume the PKCE session. Single-use and atomic: see + // SessionStore.Consume (KEY-WP-0016-T01). + sess, ok := h.Sessions.Consume(code) + if !ok { + profileerrors.InvalidProfileUsage( + "authorization code not found or expired", + "code", + ).Write(w, http.StatusBadRequest) + return + } + + // Verify client_id matches the session. + if sess.ClientID != clientID { + profileerrors.InvalidProfileUsage( + "client_id does not match the authorization code", + "client_id", + ).Write(w, http.StatusBadRequest) + return + } + + // Bind the exchange to the redirect URI the code was issued for + // (RFC 6749 section 4.1.3, KEY-WP-0016-T02). /authorize always records an + // exactly-matched registered redirect, so the parameter is always required + // here and must be identical. + if redirectURI := r.FormValue("redirect_uri"); redirectURI != sess.RedirectURI { + profileerrors.InvalidProfileUsage( + "redirect_uri does not match the authorization request", + "redirect_uri", + ).Write(w, http.StatusBadRequest) + return + } + + // Recheck grants in case the client registration changed after authorization. + for _, scope := range sess.Scopes { + if !containsString(h.ClientConfig[clientID].AllowedScopes, scope) { + profileerrors.InvalidProfileUsage("requested scope is not allowed", "scope").Write(w, http.StatusBadRequest) + return + } + } + + // 4. Verify PKCE code_verifier. + if !verifyPKCE(codeVerifier, sess.PKCEChallenge) { + profileerrors.InvalidProfileUsage( + "code_verifier does not match code_challenge", + "code_verifier", + ).Write(w, http.StatusBadRequest) + return + } + + // 5. Look up user. + user, err := h.Users.LookupUser(ctx, sess.Username) + if err != nil { + http.Error(w, "user not found", http.StatusInternalServerError) + return + } + if isSuspended(user) { + profileerrors.RejectedForSafety( + "account is suspended", + "account_lifecycle", + ).Write(w, http.StatusForbidden) + return + } + + // 6. Build JWT claims. + now := time.Now() + exp := now.Add(h.TokenLifetime) + + claims := map[string]interface{}{ + "iss": h.Issuer, + "sub": user.ID, + "aud": clientID, + "exp": exp.Unix(), + "iat": now.Unix(), + } + if sess.Nonce != "" { + claims["nonce"] = sess.Nonce + } + + scopeSet := make(map[string]bool) + for _, s := range sess.Scopes { + scopeSet[s] = true + } + + if scopeSet["profile"] { + claims["preferred_username"] = user.Username + } + if scopeSet["email"] { + claims["email"] = user.Email + } + + // Core claims required by net-kingdom/canon/standards/iam-profile_v0.3.md + // for every production token -- not scope-gated, unlike the recommended + // human claims above (KEY-WP-0005-T01). + tenant, tenantSource, err := humanTenant(h.ClientConfig[clientID], user) + if err != nil { + profileerrors.RejectedForSafety( + "tenant binding conflict", + "tenant_binding", + ).Write(w, http.StatusForbidden) + return + } + claims["tenant"] = tenant + claims["tenant_source"] = tenantSource + claims["principal_type"] = "human" + claims["groups"] = nonNilStrings(user.Groups) + claims["roles"] = nonNilStrings(user.Roles) + claims["assurance"] = assuranceClaim(sess.MFAVerified, sess.AuthTime, now) + + // Optional cached tenant_roles claim (KEY-WP-0005-T02). Fails open -- + // see internal/adapters/tenantengine's package doc for why this is the + // one place in the whole tenant_roles design where that's correct. + if roles, ok := h.TenantEngine.Roles(ctx, tenant); ok { + claims["tenant_roles"] = roles + } + + // 7. Sign JWT with RSA-SHA256. + kid := "key-1" // static kid for v0.1 + jwtToken, err := buildJWT(claims, kid, h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return + } + + // Access tokens target the statically registered resource server. ID tokens + // remain bound to the OIDC relying party. + if audience := h.ClientConfig[clientID].Audience; audience != "" { + claims["aud"] = audience + } + claims["scope"] = strings.Join(sess.Scopes, " ") + accessToken, err := buildJWT(claims, kid, h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return + } + + // 8. Build response. The session was already consumed at lookup, so no + // separate replay-prevention delete is needed here. + resp := tokenResponse{ + AccessToken: accessToken, + TokenType: "Bearer", + ExpiresIn: int(h.TokenLifetime.Seconds()), + IDToken: jwtToken, + } + + // 10. Emit token_issued telemetry. + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: time.Now(), + EventType: telemetry.EventTokenIssued, + ClientID: clientID, + Endpoint: "/token", + Result: "success", + Scopes: sess.Scopes, + GrantType: grantType, + }) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} + +// basicClientCredentials reads client_secret_basic credentials, applying the +// form-encoding decode RFC 6749 appendix B requires of both halves. Shared by +// the service grant and confidential authorization-code client authentication. +func basicClientCredentials(r *http.Request) (clientID, clientSecret string, ok bool) { + clientID, clientSecret, ok = r.BasicAuth() + if !ok { + return "", "", false + } + decodedID, idErr := url.QueryUnescape(clientID) + decodedSecret, secretErr := url.QueryUnescape(clientSecret) + if idErr != nil || secretErr != nil { + return "", "", false + } + return decodedID, decodedSecret, true +} + +// secretsEqual compares two secrets in constant time. Digesting first keeps the +// comparison length-independent, so a wrong-length secret is indistinguishable +// from a wrong-value one. +func secretsEqual(presented, expected string) bool { + presentedDigest := sha256.Sum256([]byte(presented)) + expectedDigest := sha256.Sum256([]byte(expected)) + return subtle.ConstantTimeCompare(presentedDigest[:], expectedDigest[:]) == 1 +} + +func (h *TokenHandler) serveClientCredentials(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + clientID, clientSecret, ok := r.BasicAuth() + if !ok { + profileerrors.InvalidProfileUsage("client_secret_basic authentication required", "Authorization"). + Write(w, http.StatusUnauthorized) + return + } + clientID, idErr := url.QueryUnescape(clientID) + clientSecret, secretErr := url.QueryUnescape(clientSecret) + if idErr != nil || secretErr != nil { + profileerrors.InvalidProfileUsage("invalid client authentication encoding", "Authorization").Write(w, http.StatusUnauthorized) + return + } + client, ok := h.ClientConfig[clientID] + if !ok || client.ClientType != "confidential" || !containsString(client.GrantTypes, "client_credentials") { + profileerrors.InvalidProfileUsage("invalid confidential client", "client_id"). + Write(w, http.StatusUnauthorized) + return + } + presentedDigest := sha256.Sum256([]byte(clientSecret)) + expectedDigest := sha256.Sum256([]byte(client.ClientSecret)) + if client.ClientSecret == "" || + subtle.ConstantTimeCompare(presentedDigest[:], expectedDigest[:]) != 1 { + profileerrors.InvalidProfileUsage("invalid client authentication", "Authorization"). + Write(w, http.StatusUnauthorized) + return + } + + requestedScopes := strings.Fields(r.FormValue("scope")) + if len(requestedScopes) == 0 { + requestedScopes = append([]string(nil), client.AllowedScopes...) + } + for _, scope := range requestedScopes { + if !containsString(client.AllowedScopes, scope) { + profileerrors.InvalidProfileUsage("requested scope is not allowed", "scope"). + Write(w, http.StatusBadRequest) + return + } + } + + now := time.Now() + tokenLifetime := h.TokenLifetime + if client.TokenLifetime > 0 { + tokenLifetime = client.TokenLifetime + } + claims := map[string]interface{}{ + "iss": h.Issuer, + "sub": client.ServiceSubject, + "aud": accessAudience(client), + "exp": now.Add(tokenLifetime).Unix(), + "iat": now.Unix(), + "tenant": client.Tenant, + // A service client's tenant is always registration-supplied: there is no + // directory principal behind it to assert one (GH-DEC-2026-013 §5). + "tenant_source": TenantSourceRegistration, + "principal_type": "service", + "groups": []string{}, + "roles": nonNilStrings(client.Roles), + "scope": strings.Join(requestedScopes, " "), + "assurance": map[string]interface{}{ + "level": "aal1", "methods": []string{"client_secret"}, + "mfa": false, "source": "key-cape", "at": now.Unix(), + }, + } + if roles, ok := h.TenantEngine.Roles(ctx, client.Tenant); ok { + claims["tenant_roles"] = roles + } + jwtToken, err := buildJWT(claims, "key-1", h.SigningKey) + if err != nil { + http.Error(w, "failed to build JWT", http.StatusInternalServerError) + return + } + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: now, EventType: telemetry.EventTokenIssued, ClientID: clientID, + Endpoint: "/token", Result: "success", Scopes: requestedScopes, + GrantType: "client_credentials", + }) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(tokenResponse{ + AccessToken: jwtToken, TokenType: "Bearer", + ExpiresIn: int(tokenLifetime.Seconds()), + }) +} + +func containsString(values []string, wanted string) bool { + for _, value := range values { + if value == wanted { + return true + } + } + return false +} + +func isSuspended(user *domain.User) bool { + return containsString(user.Groups, "netkingdom-suspended") +} + +// --------------------------------------------------------------------------- +// IAM Profile core claims (KEY-WP-0005-T01) +// --------------------------------------------------------------------------- + +// defaultTenant is the fallback tenant claim for users with no explicit +// Tenant assignment yet. This workstation currently operates a single +// tenant (tenant:coulomb, ADR-0006); later tenants (e.g. tenant:friendly:binky, +// ADR-0013) require an explicit domain.User.Tenant value -- this default +// never silently assigns a user to a tenant other than the platform's +// original one. +const defaultTenant = "tenant:coulomb" + +// effectiveTenant resolves the tenant claim for a user, falling back to +// defaultTenant when the user has no explicit tenant assignment. The IAM +// Profile requires a non-empty tenant claim on every token. +func effectiveTenant(user *domain.User) string { + if user.Tenant != "" { + return user.Tenant + } + return defaultTenant +} + +// humanTenant resolves the tenant claim for a human token (KEY-WP-0013-T05). +// +// A human's tenant is normally a property of the person, read from the +// directory. But the approval chain is bound to the landlord zone by decision +// 5ed3fb35-eca9-413a-82b9-95171ba85bf6, and approval-engine compares the claim +// by exact string equality, so an approver client has to be able to state the +// zone it issues into. Without this every human token fell back to +// defaultTenant and would have been refused downstream -- as a failed approval +// rather than as a registration defect. +// +// The rule is deliberately not an override: +// +// - no client tenant declared -> the directory answer, unchanged; +// - declared, user unassigned -> the declared zone; +// - declared and equal -> agreement, no ambiguity; +// - declared and different -> refuse to issue. +// +// So a registration can bind a zone for users the directory has not placed, and +// can never relabel a user the directory HAS placed into a different one. That +// last case fails closed rather than picking a winner, because either answer +// would be a silent cross-tenant assertion. It also means this stays correct if +// the directory later populates Tenant: the same code turns from supplying the +// zone into enforcing agreement with it, with no second migration. +// +// This is only safe because client registrations are static and +// deployment-owned; KeyCape excludes dynamic client registration by design. A +// self-service client that could name its users' tenant would be an escalation. +func humanTenant(client *domain.Client, user *domain.User) (string, string, error) { + if client == nil || client.Tenant == "" { + if user.Tenant != "" { + return user.Tenant, TenantSourceDirectory, nil + } + return defaultTenant, TenantSourceDefault, nil + } + if user.Tenant != "" { + if user.Tenant != client.Tenant { + return "", "", fmt.Errorf("client %q binds tenant %q but the directory assigns this user a different tenant", client.ClientID, client.Tenant) + } + // Agreement: the directory did assert this about the person, so the + // stronger provenance is the true one. + return user.Tenant, TenantSourceDirectory, nil + } + return client.Tenant, TenantSourceRegistration, nil +} + +// Tenant provenance values for the tenant_source claim (GH-DEC-2026-013 §5). +// +// A bare tenant string cannot tell a consumer whether the identity layer +// asserted the zone about this PERSON or a registration supplied it about the +// CLIENT they came through. approval-engine admits an approver by exact-matching +// that string while its contract reads as though it relies on the first, so the +// check is sound and the property a reader infers from it is absent. The ruling +// requires the claim to carry its provenance and forbids a consumer treating the +// two as equivalent for any decision turning on a fact about the person. It names +// the property; the field is ours. +// +// Three values, not the two the ruling names, and the third is the point. A +// tenant nobody asserted -- neither directory nor registration, just the +// profile's non-empty default -- is not directory-asserted, and labelling it so +// would reintroduce the same defect one level down: a consumer would read +// "directory" for a fact the directory never stated. That is the unknown-versus- +// absent distinction the ruling cites from GH-DEC-2026-011 §3, applied to our own +// fallback rather than only to the case we were asked about. +const ( + // TenantSourceDirectory: the identity layer asserted this zone about this + // person. Includes the agreement case, where a registration declared the + // same zone the directory did -- the directory still asserted it. + TenantSourceDirectory = "directory" + + // TenantSourceRegistration: supplied by the client registration for a person + // the directory has placed nowhere. A fact about the client, not the person. + TenantSourceRegistration = "registration" + + // TenantSourceDefault: nobody asserted a zone; this is the profile default, + // emitted because the profile requires a non-empty tenant. Weaker than both. + TenantSourceDefault = "default" +) + +// nonNilStrings returns s, or an empty (non-nil) slice if s is nil, so the +// claim always serializes as `[]`, never `null` -- the profile requires +// groups/roles to be present, "possibly empty", not absent. +func nonNilStrings(s []string) []string { + if s == nil { + return []string{} + } + return s +} + +// assuranceClaim builds the profile's `assurance` object from whether MFA +// was actually verified during this authorization (session.MFAVerified), +// not from static enrollment state -- a user who has MFA enrolled but +// wasn't challenged for it in this particular flow gets aal1, not aal2. +// +// `at` is the time the user authenticated, not the time this token was +// minted. Those differ whenever a browser session is reused, and the gap is +// the whole point: approval-engine stores this object verbatim as the only +// downstream record that MFA occurred (KEY-WP-0013-T05), so mint time would +// overstate how recently the person proved anything. issuedAt is the +// fallback for a session predating this field. +func assuranceClaim(mfaVerified bool, authTime, issuedAt time.Time) map[string]interface{} { + at := authTime + if at.IsZero() { + at = issuedAt + } + level := "aal1" + methods := []string{"pwd"} + if mfaVerified { + level = "aal2" + methods = append(methods, "otp") + } + return map[string]interface{}{ + "level": level, + "methods": methods, + "mfa": mfaVerified, + "source": "key-cape", + "at": at.Unix(), + } +} + +// --------------------------------------------------------------------------- +// PKCE verification +// --------------------------------------------------------------------------- + +// verifyPKCE checks BASE64URL(SHA256(verifier)) == challenge (S256 method). +func verifyPKCE(verifier, challenge string) bool { + h := sha256.New() + h.Write([]byte(verifier)) + computed := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) + return computed == challenge +} + +// --------------------------------------------------------------------------- +// JWT construction (stdlib only — no external JWT library) +// --------------------------------------------------------------------------- + +type jwtHeader struct { + Alg string `json:"alg"` + Typ string `json:"typ"` + Kid string `json:"kid"` +} + +// buildJWT constructs and signs a JWT using RSA-SHA256 with the standard library. +// Format: base64url(header) + "." + base64url(payload) + "." + base64url(signature) +func buildJWT(claims map[string]interface{}, kid string, key *rsa.PrivateKey) (string, error) { + // Header. + hdr := jwtHeader{Alg: "RS256", Typ: "JWT", Kid: kid} + hdrJSON, err := json.Marshal(hdr) + if err != nil { + return "", err + } + hdrB64 := base64.RawURLEncoding.EncodeToString(hdrJSON) + + // Payload. + payloadJSON, err := json.Marshal(claims) + if err != nil { + return "", err + } + payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON) + + // Signing input. + signingInput := hdrB64 + "." + payloadB64 + + // Digest. + digest := sha256.Sum256([]byte(signingInput)) + + // Sign with PKCS1v15 / SHA256. + sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, digest[:]) + if err != nil { + return "", err + } + sigB64 := base64.RawURLEncoding.EncodeToString(sig) + + return strings.Join([]string{hdrB64, payloadB64, sigB64}, "."), nil +} + +// accessAudience is configured by the issuer, never selected by request input. +func accessAudience(client *domain.Client) string { + if client.Audience != "" { + return client.Audience + } + return client.ClientID +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/userinfo.go b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/userinfo.go new file mode 100644 index 0000000..c3d2014 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/src/internal/server/oidc/userinfo.go @@ -0,0 +1,262 @@ +package oidc + +import ( + "context" + "crypto" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "net/http" + "strings" + "time" + + "keycape/internal/domain" + "keycape/internal/server/telemetry" +) + +// UserinfoHandler implements GET /userinfo (OIDC Core §5.3). +// +// The endpoint validates the Bearer token, extracts the subject, looks up +// the user, and returns claims that are consistent with those in the ID token +// for the same scope set. +type UserinfoHandler struct { + Users domain.UserRepository + SigningKey *rsa.PublicKey // used to verify the incoming access token + Issuer string + Emitter telemetry.Emitter +} + +// ServeHTTP handles GET /userinfo. +func (h *UserinfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // 1. Extract Bearer token. + tokenStr, ok := bearerToken(r) + if !ok { + http.Error(w, `{"error":"missing_token","description":"Authorization: Bearer required"}`, http.StatusUnauthorized) + return + } + + // 2. Validate token (algorithm, signature, expiry, issuer, purpose) and + // extract claims. + claims, err := validateAccessToken(tokenStr, h.SigningKey, h.Issuer) + if err != nil { + http.Error(w, `{"error":"invalid_token","description":"token validation failed"}`, http.StatusUnauthorized) + return + } + + // 3. Extract sub claim (which is the username in our model). + sub, _ := claims["sub"].(string) + if sub == "" { + http.Error(w, `{"error":"invalid_token","description":"missing sub claim"}`, http.StatusUnauthorized) + return + } + + // 4. Resolve the canonical subject. Human tokens use the stable directory + // ID (the LDAP DN) as sub, while LookupUser accepts an LDAP uid. Prefer the + // scoped username hint but require it to resolve back to the signed subject; + // fall back to an ID scan for openid-only tokens and renamed users. + user, err := lookupUserBySubject(ctx, h.Users, claims, sub) + if err != nil { + // User referenced in token but not found → treat as invalid token. + http.Error(w, `{"error":"invalid_token","description":"subject not found"}`, http.StatusUnauthorized) + return + } + if isSuspended(user) { + http.Error(w, `{"error":"invalid_token","description":"subject is suspended"}`, http.StatusUnauthorized) + return + } + + // 5. Build response claims filtered by the scopes embedded in the token. + scopeStr, _ := claims["scope"].(string) + scopeSet := parseScopeSet(scopeStr) + + resp := map[string]interface{}{ + "sub": sub, + } + + if scopeSet["profile"] { + resp["preferred_username"] = user.Username + resp["name"] = user.DisplayName + } + if scopeSet["email"] { + resp["email"] = user.Email + } + if scopeSet["groups"] { + resp["groups"] = user.Groups + } + + // 6. Emit telemetry. + h.Emitter.Emit(ctx, telemetry.Event{ + Timestamp: time.Now(), + EventType: telemetry.EventAuthSuccess, + Endpoint: "/userinfo", + Result: "success", + }) + + // 7. Write JSON response. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(resp) +} + +func lookupUserBySubject( + ctx context.Context, + users domain.UserRepository, + claims map[string]interface{}, + sub string, +) (*domain.User, error) { + if username, _ := claims["preferred_username"].(string); username != "" { + if user, err := users.LookupUser(ctx, username); err == nil && user.ID == sub { + return user, nil + } + } + + // Retain compatibility with older signed tokens whose subject was the uid. + if user, err := users.LookupUser(ctx, sub); err == nil && + (user.ID == sub || user.Username == sub) { + return user, nil + } + + all, err := users.ListUsers(ctx) + if err != nil { + return nil, err + } + for i := range all { + if all[i].ID == sub { + return &all[i], nil + } + } + return nil, domain.ErrUserNotFound +} + +// --------------------------------------------------------------------------- +// JWT validation (stdlib only — no external JWT library) +// --------------------------------------------------------------------------- + +// validateAccessToken parses and validates a KeyCape-issued access token. +// +// Beyond signature and expiry it enforces the bindings the caller CLI already +// requires (KEY-WP-0016-T03): the JOSE header algorithm must be exactly RS256, +// so an "alg":"none" or HMAC-shaped token can never bypass the RSA check; the +// issuer claim must equal this issuer, so a correctly-signed token from another +// deployment is refused; and the token must be an access token. +// +// Purpose is decided on the presence of the `scope` claim, which the token +// endpoint sets on access tokens and never on ID tokens. That keeps the check +// verification-side: it does not add a claim to the issued token contract, which +// consumers pin exactly. +func validateAccessToken(tokenStr string, pubKey *rsa.PublicKey, issuer string) (map[string]interface{}, error) { + parts := strings.Split(tokenStr, ".") + if len(parts) != 3 { + return nil, errors.New("malformed JWT: expected 3 parts") + } + + // Verify the JOSE header algorithm before trusting the signature check. + headerJSON, err := base64.RawURLEncoding.DecodeString(parts[0]) + if err != nil { + return nil, errors.New("malformed JWT: invalid header encoding") + } + var header struct { + Alg string `json:"alg"` + } + if err := json.Unmarshal(headerJSON, &header); err != nil { + return nil, errors.New("malformed JWT: header is not valid JSON") + } + if header.Alg != "RS256" { + return nil, errors.New("unsupported JWT algorithm") + } + + // Verify signature over header.payload. + signingInput := parts[0] + "." + parts[1] + digest := sha256.Sum256([]byte(signingInput)) + + sigBytes, decodeErr := base64.RawURLEncoding.DecodeString(parts[2]) + if decodeErr != nil { + return nil, errors.New("malformed JWT: invalid signature encoding") + } + + if err := rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, digest[:], sigBytes); err != nil { + return nil, errors.New("JWT signature verification failed") + } + + // Decode payload. + payloadJSON, payloadErr := base64.RawURLEncoding.DecodeString(parts[1]) + if payloadErr != nil { + return nil, errors.New("malformed JWT: invalid payload encoding") + } + + var claims map[string]interface{} + if err := json.Unmarshal(payloadJSON, &claims); err != nil { + return nil, errors.New("malformed JWT: payload is not valid JSON") + } + + // Check exp claim. + exp, ok := claims["exp"].(float64) + if !ok { + return nil, errors.New("JWT missing exp claim") + } + if time.Now().Unix() > int64(exp) { + return nil, errors.New("JWT has expired") + } + + // Require this issuer. An empty configured issuer would make the check + // vacuous, so treat that as a misconfiguration rather than skipping it. + if issuer == "" { + return nil, errors.New("issuer is not configured") + } + if tokenIssuer, _ := claims["iss"].(string); tokenIssuer != issuer { + return nil, errors.New("JWT issuer mismatch") + } + + // Require an access token. ID tokens carry no scope claim. + if _, ok := claims["scope"]; !ok { + return nil, errors.New("JWT is not an access token") + } + + return claims, nil +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +// bearerToken extracts the token from the Authorization header. +// Returns ("", false) when the header is missing or not a Bearer token. +func bearerToken(r *http.Request) (string, bool) { + hdr := r.Header.Get("Authorization") + if hdr == "" { + return "", false + } + const prefix = "Bearer " + if !strings.HasPrefix(hdr, prefix) { + return "", false + } + tok := strings.TrimSpace(hdr[len(prefix):]) + if tok == "" { + return "", false + } + return tok, true +} + +// parseScopeSet converts a space-separated scope string to a set. +func parseScopeSet(scope string) map[string]bool { + set := make(map[string]bool) + for _, s := range strings.Fields(scope) { + set[s] = true + } + return set +} + +// --------------------------------------------------------------------------- +// BuildJWT — exported for test helpers +// --------------------------------------------------------------------------- + +// BuildJWT is an exported wrapper around the internal buildJWT function so +// that tests in the oidc_test package can construct valid tokens for the +// UserinfoHandler without importing an external JWT library. +func BuildJWT(claims map[string]interface{}, kid string, key *rsa.PrivateKey) (string, error) { + return buildJWT(claims, kid, key) +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/workplans/ADHOC-2026-09-27.md b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/workplans/ADHOC-2026-09-27.md new file mode 100644 index 0000000..0b75554 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/preimages/workplans/ADHOC-2026-09-27.md @@ -0,0 +1,100 @@ +--- +id: ADHOC-2026-09-27 +type: workplan +title: "Fresh MFA no longer backdated to a stale kc_login cookie (INFD-IN-0005)" +domain: infotech +repo: key-cape +status: finished +owner: claude +created: "2026-09-27" +updated: "2026-09-27" +state_hub_workstream_id: "5217dd06-73cb-50f8-ba64-528e421b29de" +--- + +## Stop completeAuthorization from reusing a stale prior session's IssuedAt after real MFA + +```task +id: ADHOC-2026-09-27-T01 +status: done +priority: high +state_hub_task_id: "ce98e4af-cfaa-5620-8f21-15ade18ee9e8" +``` + +`informed-decision` intake `INFD-IN-0005` (open, `origin_ref: SECRETS-WP-0010`): +platform-root's approval of `infd-20260927-b01` was refused by flex-auth's +`compact_sitting` policy with `policy_denied`, despite platform-root having just +completed a fresh key-cape MFA sign-in. flex-auth's 900-second review-freshness +check reads `assurance.at`, which key-cape derives from the PKCE session's +`AuthTime`. + +Root cause in `src/internal/server/oidc/authorize.go`, +`AuthorizeHandler.completeAuthorization`: whenever a `kc_login` cookie already +existed for the same username, its `IssuedAt` was unconditionally reused as +`authTime` — even on the two call sites that had just called +`MFA.ValidateMFAToken` successfully in this exact request (the callback's +`mfaToken != ""` branch and `serveMFASubmission`). A user who had an older +browser session and then stepped up with a fresh MFA challenge got the *old* +session's timestamp stamped onto the *new*, just-proven authentication — +understating freshness for every downstream binding-grade check, exactly as +`INFD-IN-0005` describes ("completeAuthorization currently copies a prior +timestamp... only after actual reauthentication"). + +The reuse itself is correct when MFA was **not** just performed in this +request (`decision.MFAVerified` carried forward from an existing AAL2 session, +via `domain.DecideAssurance`) — reporting mint time there would overstate +freshness (`TestAssuranceReportsAuthenticationTimeNotMintTime`). The two cases +were conflated under a single `mfaVerified` bool. + +Fix: added a `freshMFA bool` parameter to `completeAuthorization`, `true` only +at the two call sites that just validated an MFA token in this request, `false` +at the carried-forward-assurance call site. `authTime` now falls back to the +prior session's `IssuedAt` only when `!freshMFA`. + +Added `TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie`, +which seeds a 4-hour-old `kc_login` cookie for the same user, then submits a +valid MFA token and asserts the resulting `AuthTime` is within a minute of now. +Confirmed it fails against the pre-fix code (`AuthTime` ~4h old) and passes +after. Full suite (`go test ./...`) green, no other test needed adjustment — +the carried-forward-assurance case (`TestPolicy_...`, `TestLogout_...`, +`assurance_time_test.go`) is unaffected since none of it exercises a fresh MFA +submission with a pre-existing cookie for the same user. + +Does not touch flex-auth (its 900s window is correct; it was consuming a bad +timestamp) or informed-decision (correctly refuses to interpret the PDP's +denial). `INFD-IN-0005` can close once informed-decision confirms platform-root +can approve after a fresh sign-in against this build. + +## Discovery/JWKS fetch was missing the split-horizon origin headers + +```task +id: ADHOC-2026-09-27-T02 +status: done +priority: high +``` + +Rolling out T01 (image digest `sha256:248e449a...`) broke every KeyCape login, +including unrelated clients (`user-engine-portal`), with `auth_failure` / +`provider_metadata_unavailable` at `/api/oidc/token` on the KeyCape side and +Authelia logging `invalid X-Forwarded-Proto header value 'http'` on +`GET /.well-known/openid-configuration` from KeyCape's pod IP. + +Root cause, unrelated to T01's change: `AutheliaAdapter.setPublicOriginHeaders` +(the NK-WP-0041-T02/path-B fix, key-cape@3b0446e) annotates only the token +exchange (`newTokenRequest`) with `X-Forwarded-Proto`/`X-Forwarded-Host` so +Authelia can derive the public issuer for a split-horizon (in-cluster HTTP, +public HTTPS) deployment. `idTokenVerifier.get` in `verify.go` — used for the +provider-metadata and JWKS fetches that verify every upstream ID token — never +got the same treatment; it built a bare request with only an `Accept` header. +Authelia 4.38 fails closed on the resulting unmarked in-cluster request. This +gap predates today's session (present since 3b0446e); it surfaced now because +this was the first login attempted since that image went live. + +Fix: extracted `setPublicOriginHeaders` from an `AutheliaAdapter` method to a +package-level function taking the target public base URL explicitly, and gave +`idTokenVerifier` its own resolved `publicBaseURL` (same +`BrowserBaseURL`-else-`BaseURL` fallback as the adapter) so `get()` applies +the same headers. Added `TestDiscoveryFetch_CarriesPublicOriginHeaders` +(confirmed it fails without the fix, passes with it). Full suite green. + +New image `key-cape@` still needs a rollout to `sso-mfa/k8s/keycape/deployment.yaml` +before platform-root can sign in again. diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/restore_owner_generated_brief.py b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/restore_owner_generated_brief.py new file mode 100644 index 0000000..db0f063 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/restore_owner_generated_brief.py @@ -0,0 +1,30 @@ +"""Restore only this turn's generated staged brief after successful sync.""" +from pathlib import Path +import hashlib +import json +import subprocess + +HERE = Path(__file__).resolve().parent +ROOT = Path('/home/worsch/key-cape') +before = json.loads((HERE / 'owner-consistency/before.json').read_text()) +backup = HERE / 'owner-publication/publication-backup' +row = next(item for item in json.loads((backup / 'originals.json').read_text()) if item['path'] == '.custodian-brief.md') +def git(*args): + return subprocess.check_output(['git', '-C', str(ROOT), *args], text=True).strip() +assert json.loads((HERE / 'owner-publication/projection.json').read_text())['status'] == 'applied' +assert (ROOT / row['path']).read_bytes() == (backup / (row['path'] + '.worktree')).read_bytes() +assert git('ls-files', '--stage', '--', row['path']) == row['index'] +original = subprocess.check_output(['git', '-C', str(ROOT), 'show', before['head'] + ':' + row['path']]) +initial = before['tracked'][row['path']] +assert 'sha256:' + hashlib.sha256(original).hexdigest() == initial['sha256'] +mode, oid, stage, name = initial['index'].split() +assert stage == '0' and name == row['path'] and not before['tracked_status'] +(ROOT / row['path']).write_bytes(original) +subprocess.run(['git', '-C', str(ROOT), 'update-index', '--cacheinfo', mode, oid, name], check=True) +assert git('ls-files', '--stage', '--', name) == initial['index'] +assert 'sha256:' + hashlib.sha256((ROOT / name).read_bytes()).hexdigest() == initial['sha256'] +receipt = {'restored': name, 'scope': 'only this turn generated it; initial owner tracked state was clean', + 'original_owner_worktree_and_index_preserved': True, 'source_commit_unchanged': git('rev-parse', 'HEAD'), + 'tracked_status': git('status', '--porcelain=v1', '--untracked-files=no')} +(HERE / 'owner-generated-cleanup.json').write_text(json.dumps(receipt, indent=2) + '\n') +print(json.dumps(receipt)) diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/run_candidate.py b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/run_candidate.py new file mode 100644 index 0000000..168a63d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/run_candidate.py @@ -0,0 +1,72 @@ +"""Run issuer source checks with no host network, HOME, or credentials.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import subprocess +import time + +HERE = Path(__file__).resolve().parent +parser = argparse.ArgumentParser() +parser.add_argument("mode", choices=["focused", "full", "build", "binary"]) +parser.add_argument("--attempt", type=int, default=1) +parser.add_argument("--source", type=Path, default=Path("/tmp/custodian-keycape-token-status-r1/source")) +parser.add_argument("--cache-from", type=Path) +args = parser.parse_args() +source = args.source.resolve() +assert source.is_relative_to("/tmp") and (source / "src/go.mod").is_file() +workspace = source.parent +(workspace / "home").mkdir(exist_ok=True) +toolchain = Path("/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64") +argv = ["bwrap", "--unshare-net", "--unshare-pid", "--die-with-parent", "--new-session"] +for name in ["/usr", "/lib", "/lib64"]: + argv += ["--ro-bind", name, name] +argv += ["--symlink", "usr/bin", "/bin", "--proc", "/proc", "--dev", "/dev", "--tmpfs", "/tmp", + "--bind", str(workspace), "/work", "--ro-bind", "/home/worsch/go/pkg/mod", "/modules", + "--ro-bind", str(toolchain), "/toolchain", "--chdir", "/work/" + source.name + "/src", "--clearenv"] +if args.mode == "binary": + argv += ["--ro-bind", str(HERE / 'binary_fixture.py'), "/fixture.py"] +if args.cache_from: + cache = args.cache_from.resolve() + assert cache.is_relative_to('/tmp') and cache.is_dir() + argv += ['--bind', str(cache), '/work/gocache'] +for key, value in {"PATH": "/toolchain/bin:/usr/bin:/bin", "HOME": "/work/home", "GOTOOLCHAIN": "local", + "GOPROXY": "off", "GOSUMDB": "off", "GOMODCACHE": "/modules", "GOCACHE": "/work/gocache", + "GOMAXPROCS": "4", "CGO_ENABLED": "1"}.items(): + argv += ["--setenv", key, value] +if args.mode == "binary": + command = ["python3", "/fixture.py"] +elif args.mode == "build": + command = ["go", "build", "-o", "/work/keycape-candidate", "./cmd/keycape"] +else: + command = ["go", "test", "-p", "4", "-race", "-json", "-count=1"] + command += ["-run", "^TestTokenStatus", "./internal/server/oidc", "./internal/config"] if args.mode == "focused" else ["./..."] +argv += ["--", *command] +prefix = HERE / f"{args.mode}-attempt{args.attempt}" +assert not prefix.with_suffix(".json").exists() +start = time.monotonic() +source_inventory = {str(p.relative_to(source)): hashlib.sha256(p.read_bytes()).hexdigest() + for p in (source / 'src').rglob('*') if p.is_file()} +with prefix.with_suffix(".jsonl").open("xb") as output: + result = subprocess.run(argv, stdout=output, stderr=subprocess.STDOUT, timeout=900) +counts = {"pass": 0, "fail": 0, "skip": 0} +failures, skips = [], [] +for line in prefix.with_suffix(".jsonl").read_text().splitlines(): + try: + event = json.loads(line) + except ValueError: + continue + if event.get("Test") and event.get("Action") in counts: + action = event["Action"] + counts[action] += 1 + if action == "fail": failures.append(event["Package"] + ":" + event["Test"]) + if action == "skip": skips.append(event["Package"] + ":" + event["Test"]) +receipt = {"returncode": result.returncode, "seconds": round(time.monotonic() - start, 2), "argv": argv, + "source_sha256_at_start": source_inventory, + "counts_including_subtests": counts, "failures": failures, "skips": skips, + "source": str(source), "native_acceptance": False, "credentials_accessed": False, + "network": "private empty namespace; fixture loopback only", "home": "new empty sandbox HOME"} +prefix.with_suffix(".json").write_text(json.dumps(receipt, indent=2) + "\n") +print(json.dumps({k: receipt[k] for k in ["returncode", "seconds", "counts_including_subtests", "failures", "skips"]})) +raise SystemExit(result.returncode) diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-check-equivalence.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-check-equivalence.json new file mode 100644 index 0000000..c31b7f6 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-check-equivalence.json @@ -0,0 +1,5 @@ +{ + "full_race_test_source_matches_built_and_binary_test_source": true, + "source_files": 115, + "binary_sha256": "sha256:bc1369cf122133642180a9fa3cf92b93e0332f639c4e218d5041af959c77c92b" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-receipt.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-receipt.json new file mode 100644 index 0000000..60e2041 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-receipt.json @@ -0,0 +1,18 @@ +{ + "workplan_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52", + "id": "1a76c40a-bdfe-4bc4-b81d-526ee12105c9", + "topic_id": null, + "title": "Prepare online access-token status in the existing issuer; retain independent operational admission", + "description": "Source preparation under the existing full CUST-WP-0080 implementation goal extends KeyCape with an explicit opt-in RFC 7662/7009 profile, rather than duplicating a token registry in Forgejo/Warden. KEY-WP-0042 is proposed; issuer/profile owner assent and native admission remain open. Existing Access/Approval/Warden/Apps operation, effect and cleanup authority is unchanged. Restore the historical KeyCape task file reference only after exact parent/record/title/status/description correspondence with its already-existing Hub binding; create no replacement identity.", + "decision_type": "made", + "status": "resolved", + "rationale": "Actual owner contracts establish that legacy JWT expiry and an injected evidence callback do not supply current revocation. A single producer facility preserves ownership and standard transport contracts. Bounded source, race, HTTPS and actual-process proofs are retained; process-local restart invalidation and offline-consumer limits are explicit. This record authorizes no live credential, trust, package, policy, or deployment change.", + "decided_by": "codex (authorized source preparation only)", + "decided_at": "2026-10-18T08:16:18.615371Z", + "deadline": null, + "escalation_note": null, + "superseded_by": null, + "created_at": "2026-10-18T08:16:19.437517Z", + "updated_at": "2026-10-18T08:16:19.437517Z", + "workstream_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-request.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-request.json new file mode 100644 index 0000000..07e5d25 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-decision-request.json @@ -0,0 +1,11 @@ +{ + "id": "1a76c40a-bdfe-4bc4-b81d-526ee12105c9", + "workplan_id": "dfadb19b-445e-59c6-bcd6-48e87b912c52", + "title": "Prepare online access-token status in the existing issuer; retain independent operational admission", + "description": "Source preparation under the existing full CUST-WP-0080 implementation goal extends KeyCape with an explicit opt-in RFC 7662/7009 profile, rather than duplicating a token registry in Forgejo/Warden. KEY-WP-0042 is proposed; issuer/profile owner assent and native admission remain open. Existing Access/Approval/Warden/Apps operation, effect and cleanup authority is unchanged. Restore the historical KeyCape task file reference only after exact parent/record/title/status/description correspondence with its already-existing Hub binding; create no replacement identity.", + "decision_type": "made", + "status": "resolved", + "rationale": "Actual owner contracts establish that legacy JWT expiry and an injected evidence callback do not supply current revocation. A single producer facility preserves ownership and standard transport contracts. Bounded source, race, HTTPS and actual-process proofs are retained; process-local restart invalidation and offline-consumer limits are explicit. This record authorizes no live credential, trust, package, policy, or deployment change.", + "decided_by": "codex (authorized source preparation only)", + "decided_at": "2026-10-18T08:16:18.615371+00:00" +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-manifest.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-manifest.json new file mode 100644 index 0000000..02528ee --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/source-manifest.json @@ -0,0 +1,107 @@ +{ + "owner": "key-cape", + "base_revision": "git:d803e9282a47b49daf930fb905312fa5104c0ace", + "files": [ + { + "path": "SCOPE.md", + "before_sha256": "sha256:20fbadf3e33339bca4b541cdfa33a0de00095c968d51006dc4d0a46735cc834f", + "sha256": "sha256:a80e80ba2e5279ba50232e6b3f57a5bdd14866e584d2d055dfb09e98a4c24ce0" + }, + { + "path": "docs/online-token-status.md", + "before_sha256": null, + "sha256": "sha256:f5e41b47500b15c80b2da6cbe37a9e64bcbde1c072750934cf74464a19488925" + }, + { + "path": "docs/operations.md", + "before_sha256": "sha256:e68ec32073b7731214308ccd2fceb522d934b00f1d76e6d438e2870030497cfc", + "sha256": "sha256:3659362e3c7f8720e52cc8dc7c67a6295ac26462f383c68c06c120fac6e6e472" + }, + { + "path": "src/cmd/keycape/main.go", + "before_sha256": "sha256:ff1ee4f5d2eb86a1e4184374d5244e24704d9d5fd780e173062af07112344afd", + "sha256": "sha256:2f81ea0b0b33f38298e028d5610625e3834fdf1aa12f6f392579d6207bcf6f1d" + }, + { + "path": "src/internal/config/config.go", + "before_sha256": "sha256:afd465acd9b6879493b888acc6fc54b9a71e032892701a81f1072986d2c30ac7", + "sha256": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683" + }, + { + "path": "src/internal/config/token_status_test.go", + "before_sha256": null, + "sha256": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5" + }, + { + "path": "src/internal/config/validate.go", + "before_sha256": "sha256:8189ee82e7df74a2a4a1f53e213b1be0a5c737fd76d8f283f7ec9f8ed38c3a65", + "sha256": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405" + }, + { + "path": "src/internal/server/oidc/discovery.go", + "before_sha256": "sha256:5ff9ddd9e2f4304b562e116100ca31fe7eabf8315d7d73998d3e6c5bea53956d", + "sha256": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5" + }, + { + "path": "src/internal/server/oidc/token.go", + "before_sha256": "sha256:8422cc9dbd39c88dfe835b43f6262cb120bc0d2e5b08898a151167b7555368d3", + "sha256": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f" + }, + { + "path": "src/internal/server/oidc/token_status.go", + "before_sha256": null, + "sha256": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3" + }, + { + "path": "src/internal/server/oidc/token_status_discovery_test.go", + "before_sha256": null, + "sha256": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031" + }, + { + "path": "src/internal/server/oidc/token_status_http.go", + "before_sha256": null, + "sha256": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97" + }, + { + "path": "src/internal/server/oidc/token_status_internal_test.go", + "before_sha256": null, + "sha256": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53" + }, + { + "path": "src/internal/server/oidc/token_status_test.go", + "before_sha256": null, + "sha256": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90" + }, + { + "path": "src/internal/server/oidc/token_status_transport_test.go", + "before_sha256": null, + "sha256": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347" + }, + { + "path": "src/internal/server/oidc/userinfo.go", + "before_sha256": "sha256:bbd5fa9ebd0726371c90d6938536c1ce5fcfacbbe853a80833e3587fc2115a0b", + "sha256": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f" + }, + { + "path": "workplans/ADHOC-2026-09-27.md", + "before_sha256": "sha256:2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314", + "sha256": "sha256:f6220f17fc5b6e7300d0a55ac29f78f385ad89db276ccaa21693428fffa5919b" + }, + { + "path": "workplans/KEY-WP-0042-online-access-token-status.md", + "before_sha256": null, + "sha256": "sha256:6d7ff6dea31b251e261195fff97a2061d56579b5786629d95014f6d1cbe0f55e" + } + ], + "patch_sha256": "sha256:e3d3bc375a8a0a49eff434483050ae338935391b5b361e64f0880ed992f4c85c", + "replay": { + "fuzz": 0, + "all_postimages_exact": true, + "all_unrelated_archive_files_unchanged": true, + "stdout": "patching file SCOPE.md\npatching file docs/online-token-status.md\npatching file docs/operations.md\npatching file src/cmd/keycape/main.go\npatching file src/internal/config/config.go\npatching file src/internal/config/token_status_test.go\npatching file src/internal/config/validate.go\npatching file src/internal/server/oidc/discovery.go\npatching file src/internal/server/oidc/token.go\npatching file src/internal/server/oidc/token_status.go\npatching file src/internal/server/oidc/token_status_discovery_test.go\npatching file src/internal/server/oidc/token_status_http.go\npatching file src/internal/server/oidc/token_status_internal_test.go\npatching file src/internal/server/oidc/token_status_test.go\npatching file src/internal/server/oidc/token_status_transport_test.go\npatching file src/internal/server/oidc/userinfo.go\npatching file workplans/ADHOC-2026-09-27.md\npatching file workplans/KEY-WP-0042-online-access-token-status.md\n", + "stderr": "" + }, + "canonical_owner_modified": false, + "independent_owner_assent": false, + "native_admission": false +} diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/type_source_digests.py b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/type_source_digests.py new file mode 100644 index 0000000..d3d632e --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/type_source_digests.py @@ -0,0 +1,36 @@ +"""Publish typed public source digests with an exact original-byte derivation.""" +import hashlib +import json +from pathlib import Path + +HERE = Path(__file__).resolve().parent +records = [] +for name, field in [('binary-attempt1.json', 'source_sha256_at_start'), + ('focused-attempt2.json', 'source_sha256_at_start'), + ('full-source-inventory.json', 'sha256')]: + file = HERE / name + raw = file.read_bytes() + value = json.loads(raw) + source = Path(value['source']).resolve() + assert source.is_relative_to('/tmp') and source.is_dir() + original = dict(value[field]) + for relative, digest in original.items(): + assert len(digest) == 64 and all(c in '0123456789abcdef' for c in digest) + path = (source / relative).resolve() + assert path.is_relative_to(source) + assert hashlib.sha256(path.read_bytes()).hexdigest() == digest + value[field] = {relative: 'sha256:' + digest for relative, digest in original.items()} + published = (json.dumps(value, indent=2) + '\n').encode() + inverse = json.loads(published) + inverse[field] = {relative: digest.removeprefix('sha256:') for relative, digest in inverse[field].items()} + assert (json.dumps(inverse, indent=2) + '\n').encode() == raw + file.write_bytes(published) + records.append({'file': name, 'field': field, 'digests_checked_against_exact_source': len(original), + 'original_bytes_sha256': 'sha256:' + hashlib.sha256(raw).hexdigest(), + 'published_bytes_sha256': 'sha256:' + hashlib.sha256(published).hexdigest(), + 'inverse_reconstructs_original_bytes': True}) +receipt = {'purpose': 'explicit digest type on public source inventories; no value secrecy transformation', + 'records': records, 'runtime_or_test_source_changed': False, 'scanner_configuration_changed': False} +(HERE / 'typed-source-digest-derivation.json').write_text(json.dumps(receipt, indent=2) + '\n') +print(json.dumps({'files': len(records), 'verified_source_digests': sum(r['digests_checked_against_exact_source'] for r in records), + 'original_bytes_exactly_reconstructable': True})) diff --git a/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/typed-source-digest-derivation.json b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/typed-source-digest-derivation.json new file mode 100644 index 0000000..c000dde --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/typed-source-digest-derivation.json @@ -0,0 +1,31 @@ +{ + "purpose": "explicit digest type on public source inventories; no value secrecy transformation", + "records": [ + { + "file": "binary-attempt1.json", + "field": "source_sha256_at_start", + "digests_checked_against_exact_source": 115, + "original_bytes_sha256": "sha256:a1feec82fe8cc84c60e545071143acc354dbd5612e823a23884af3c9ff83d858", + "published_bytes_sha256": "sha256:82e9eb100d1f0446d5412467e42833cd9da28278659fcb99078f6436a9707435", + "inverse_reconstructs_original_bytes": true + }, + { + "file": "focused-attempt2.json", + "field": "source_sha256_at_start", + "digests_checked_against_exact_source": 115, + "original_bytes_sha256": "sha256:b2792cd084fe03ac3322fb4266caee66480b259f1b8210427c2ca76a8c5bbe09", + "published_bytes_sha256": "sha256:be0b26631fb7e5c595d27f751176abe6ea3f3279167bea6dfd77d37ba5ef7793", + "inverse_reconstructs_original_bytes": true + }, + { + "file": "full-source-inventory.json", + "field": "sha256", + "digests_checked_against_exact_source": 115, + "original_bytes_sha256": "sha256:31dad565976de6ba7ed447c4a69b96c86d4c2d9e11033e30d9bc15f93c3399bb", + "published_bytes_sha256": "sha256:9bacac653f28000314e177a63a4a62dc08eda7ab957b5dd9b6b90ed60009ec7e", + "inverse_reconstructs_original_bytes": true + } + ], + "runtime_or_test_source_changed": false, + "scanner_configuration_changed": false +} diff --git a/workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md b/workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md index 5ccb226..3d2fea1 100644 --- a/workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md +++ b/workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md @@ -78,6 +78,39 @@ bootstrap callers and approved exception evidence. Reuse owner work where it already covers the finding. Record new concrete uncovered implementation work before closing this task. Never populate secret values or invent owner approval. +2026-10-18 current-token producer continuation: actual owner contracts confirm +that legacy KeyCape JWT validity is expiry-only and Warden's injected session +verifier does not supply a current revocation source. Prepared an explicit, +proposed issuer scope extension in the existing KeyCape service rather than +an application-local token registry. One opt-in bounded hash-only store serves +actual human/service issuance, UserInfo, RFC 7662 introspection and RFC 7009 +revocation. Unique access-token identifiers prevent same-second remint/revival; +ID-token and disabled-profile behavior remain unchanged. Online checks bind +current registration and human disposition and recheck after directory lookup. + +All 659 complete source race test/subtest results pass, with six existing +native/external skips; all 43 new status results pass without skips. The built +standalone server passes 26 actual-process checks, including revocation, real +restart/predecessor refusal, fresh issuance, capacity and disabled routes. A +separate actual-handler HTTPS PKCE flow passes. Omitting the revoked-state +predicate in a controlled source copy fails seven intended checks. All 18 source/publication +postimages replay exactly from the published owner archive; production is +untouched. Process-local status loss deliberately requires reauthentication; +offline consumers retain their existing token lifetime. These limits are part +of the explicit proposed KEY-WP-0042 contract, not an implicit scope promotion. + +Ordinary owner identifier preview exposed a preexisting closed historical task +without its file UUID. The Hub has an exact matching existing binding; normal +consistency audit skips creation in closed workstreams. Its existing helper +restores only that exact already-recorded reference after parent/record/title/ +status/body comparison. Normal Repo Manager assigns the four new canonical +proposal records. KeyCape `67a0869c` publishes the 17-path feature and historical +reference restoration, without changing historical status or any existing UUID. Exact source, refusals and process receipts: +`docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1/`. Independent +issuer/profile assent, native issuer admission and existing NK48/Apps operation, +inverse, ongoing-resource and cleanup bindings remain open. All sixteen target +gates, thirteen gaps and 116 ledger entries remain in scope; none closes here. + 2026-10-18 PostgreSQL continuation: the actual Forgejo engine/model schemas and OAuth configuration types now pass 37 test/subtest results on disposable PostgreSQL 16.14 across default, explicit-public and named schemas. Separate