Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0ebb401
docs(demo-client): author the contributor and integrator demo-client …
cuioss-oliver Aug 1, 2026
90eb9d7
feat(demo-client): add the module skeleton with an opt-in Playwright …
cuioss-oliver Aug 1, 2026
4ea1d51
feat(demo-client): register demo-client in the reactor
cuioss-oliver Aug 1, 2026
bbefa60
feat(demo-client): build the dependency-free demo SPA
cuioss-oliver Aug 1, 2026
60866f3
feat(integration-tests): serve the demo SPA from the two demo gateways
cuioss-oliver Aug 1, 2026
3497ee2
feat(demo-client): author the Playwright suite across both session modes
cuioss-oliver Aug 1, 2026
67cf764
chore(demo-client): ignore npm node_modules output at the repository …
cuioss-oliver Aug 2, 2026
cbbd144
chore(architecture): refresh the inventory with the demo-client virtu…
cuioss-oliver Aug 2, 2026
aba1742
fix(bff): make the browser login flow work end to end for the demo SPA
cuioss-oliver Aug 2, 2026
df0bca4
chore(simplify): collapse accidental complexity in plan-11-demo-clien…
cuioss-oliver Aug 2, 2026
06625a9
fix(security): harden demo-client npm supply chain in plan-11-demo-cl…
cuioss-oliver Aug 2, 2026
fa78676
docs(demo-client): align the documented E2E lifecycle with the harden…
cuioss-oliver Aug 2, 2026
0976b96
test(bff): adopt the unnamed catch variable and fence SameSite=Lax po…
cuioss-oliver Aug 2, 2026
b8e66d3
fix(demo-client): render a transport-level probe failure instead of l…
cuioss-oliver Aug 2, 2026
1ed98c3
fix(demo-client): guarantee CI teardown, install the browser explicit…
cuioss-oliver Aug 2, 2026
5e2d54c
docs(adr): amend ADR-0018 with the response-mode coupling and resolve…
cuioss-oliver Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .github/workflows/demo-client-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# The demo SPA's browser end-to-end suite (demo-client), run against the real Keycloak stack.
#
# DEDICATED AND OPT-IN BY DESIGN. This workflow is triggered by workflow_dispatch and by pushes to
# main — deliberately NOT by any pull_request trigger, and deliberately NOT folded into maven.yml or
# integration-tests.yml. The suite builds a native image, starts containers and downloads a browser
# toolchain; putting that on the default pull-request path would slow every PR for a signal that is
# about the demo client, not about the gateway. Keep it that way: if this ever needs to gate a PR,
# that is a decision to take explicitly, not by adding a trigger here in passing.
name: Demo Client E2E

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

# One demo run at a time — the suite drives a shared container stack on fixed host ports, so two
# concurrent runs on the same runner class would collide.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
demo-client-e2e:
name: Demo SPA browser suite (both session modes)
runs-on: ubuntu-latest
# The native compile is the dominant and most variable term; the container bring-up and the two
# Playwright projects together are minutes, not tens of minutes.
timeout-minutes: 60

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit

- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: '25'
distribution: 'temurin'
cache: maven

# Build the native executable explicitly rather than letting the bring-up script's
# build-native-if-needed.sh do it implicitly. Both produce the same artifact, but doing it as
# its own step means a native compilation failure is reported as a native compilation failure
# instead of surfacing as a container that never became ready.
- name: Build the native executable
run: |
./mvnw --no-transfer-progress clean package -Pnative -pl api-sheriff -am -DskipTests

# The e2e-demo profile owns the whole lifecycle: it installs the pinned Node toolchain and the
# pinned browser, lints, runs start-dev-environment.sh (which rebuilds the api-sheriff image
# from the executable above and brings up the trimmed three-container stack), runs both
# Playwright projects, and tears the stack down again in post-integration-test.
- name: Run the demo E2E suite
run: |
./mvnw --no-transfer-progress verify -Pe2e-demo -pl demo-client

# THE TEARDOWN SAFETY NET, and it is not redundant with the profile's own post-integration-test
# teardown. frontend-maven-plugin's npm goal fails the build IMMEDIATELY on a non-zero npm exit
# — unlike maven-failsafe-plugin it records nothing for a later phase and honours no
# testFailureIgnore — so a FAILING suite means Maven never reaches post-integration-test and
# keycloak, api-sheriff and api-sheriff-cookie are all left running. Nothing else here reclaims
# them: the job runs `verify`, not `clean verify`, so the profile's pre-clean teardown never
# fires either. `always()` covers the failing-suite path and a cancelled run alike.
#
# `|| true` is a deliberate guard, not sloppiness. This step runs on EVERY outcome, including
# after a green suite whose post-integration-test teardown already emptied the stack — so the
# nothing-left-to-stop path is the common one, and it must never turn a green job red.
# stop-dev-environment.sh is not contracted to exit 0 on that path: demo-client/pom.xml already
# declares successCodes 0 AND 1 on both of its teardown executions for exactly this reason.
# (Observed locally on docker compose v2, the nothing-to-stop path does exit 0 — the guard
# covers the tolerated 1, it does not assume it.)
- name: Tear down the demo stack
if: always()
run: |
./demo-client/scripts/stop-dev-environment.sh || true

# Diagnostics, best-effort: the JUnit XML plus Playwright's failure-path traces, screenshots
# and videos all land under target/test-results. `always()` because a failing suite is exactly
# when they are worth having, and `warn` rather than `error` because an earlier step failing
# (a native compile that never produced an image, say) legitimately leaves nothing here — that
# is already reported by the step that actually failed, and re-reporting it as a missing
# artifact would only obscure the real cause.
- name: Upload JUnit results and failure diagnostics
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: demo-client-junit-results
path: demo-client/target/test-results/
retention-days: 30
if-no-files-found: warn

# The documentation screenshot set: one parallel set per session-mode project, captured on the
# SUCCESS path at the meaningful states (anonymous, authenticated, full allowlisted view,
# claim denied, logged out). This is the artifact the demo exists to produce, so the
# assertion is deliberately strict — but scoped to `success()`, where it is meaningful: a
# suite that passed and produced no screenshots has silently stopped documenting anything,
# and that MUST fail. On a failed suite the set is legitimately incomplete, so the step does
# not run at all rather than adding a second red mark to an already-diagnosed failure.
- name: Upload documentation screenshots
if: success()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: demo-client-screenshots
path: demo-client/target/screenshots/
retention-days: 30
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ benchmarking/doc/templates/data/
.plan/*
!.plan/marshal.json
!.plan/project-architecture/
node_modules/
2 changes: 2 additions & 0 deletions .plan/project-architecture/_project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"api-sheriff": {},
"api-sheriff-parent": {},
"benchmarks": {},
"demo-client-maven": {},
"demo-client-npm": {},
"documentation": {},
"integration-tests": {}
},
Expand Down
104 changes: 104 additions & 0 deletions .plan/project-architecture/demo-client-maven/enriched.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"best_practices": [],
"insights": [],
"internal_dependencies": [],
"key_dependencies": [],
"key_dependencies_reasoning": "",
"key_packages": {},
"purpose": "e2e-tests",
"purpose_reasoning": "packaging=pom with maven.compiler.skip and skipTests true and zero Java sources; the only executions are Playwright browser tests bound to integration-test behind -Pe2e-demo",
"responsibility": "Reactor-visible shell for the demo client: a Java-free pom-packaging module that owns the dependency-free demo SPA sources and the Playwright end-to-end suite, and wires the pinned Node/npm toolchain plus the stack bring-up and teardown scripts behind the opt-in e2e-demo profile. A default reactor build of this module is a deliberate no-op - no Node download, no npm install, no container, no artifact - and nothing here ever reaches the production image or the native build.",
"responsibility_reasoning": "demo-client/pom.xml description and comments, the e2e-demo profile executions (frontend-maven-plugin install-node-and-npm, npm ci, npm run lint:strict, npm run install-browser, npm run test) and the exec-maven-plugin start/stop-dev-environment bindings",
"skills_by_profile": {
"implementation": {
"defaults": [
{
"description": "Foundational agent behavior rules (user interaction, tool usage, research, dependency management)",
"skill": "plan-marshall:persona-plan-marshall-agent"
},
{
"description": "Language-agnostic code quality, refactoring, and documentation principles",
"skill": "plan-marshall:ref-code-quality"
},
{
"description": "marshalld build-server consumption client (submit/wait/ping/preflight)",
"skill": "plan-marshall:build-server-client"
},
{
"description": "Core JavaScript standards: ES modules, modern patterns, DOM trust boundaries and XSS prevention, code quality",
"skill": "pm-dev-frontend:javascript"
},
{
"description": "ESLint flat config, Prettier and Stylelint configuration and enforcement",
"skill": "pm-dev-frontend:lint-config"
},
{
"description": "Modern CSS standards covering responsive design, quality practices and tooling",
"skill": "pm-dev-frontend:css"
}
]
},
"module_testing": {
"defaults": [
{
"description": "Foundational agent behavior rules (user interaction, tool usage, research, dependency management)",
"skill": "plan-marshall:persona-plan-marshall-agent"
},
{
"description": "Language-agnostic code quality principles (SRP, CQS, complexity, error handling)",
"skill": "plan-marshall:ref-code-quality"
},
{
"description": "Language-agnostic testing methodology (AAA, coverage, reliability, determinism)",
"skill": "plan-marshall:persona-module-tester"
},
{
"description": "Integration-testing persona for the cross-component browser suite (Playwright)",
"skill": "plan-marshall:persona-integration-tester"
},
{
"description": "marshalld build-server consumption client (submit/wait/ping/preflight)",
"skill": "plan-marshall:build-server-client"
},
{
"description": "Core JavaScript standards: ES modules, modern patterns, DOM trust boundaries and XSS prevention, code quality",
"skill": "pm-dev-frontend:javascript"
}
]
},
"quality": {
"defaults": [
{
"description": "Foundational agent behavior rules (user interaction, tool usage, research, dependency management)",
"skill": "plan-marshall:persona-plan-marshall-agent"
},
{
"description": "Language-agnostic code quality, refactoring, and documentation principles",
"skill": "plan-marshall:ref-code-quality"
},
{
"description": "Core JavaScript standards: ES modules, modern patterns, DOM trust boundaries and XSS prevention, code quality",
"skill": "pm-dev-frontend:javascript"
},
{
"description": "ESLint flat config, Prettier and Stylelint configuration and enforcement",
"skill": "pm-dev-frontend:lint-config"
}
]
},
"security": {
"defaults": [
{
"description": "JavaScript security: DOM trust boundaries, XSS sinks, sanitization and Trusted Types",
"skill": "pm-dev-frontend:javascript-security"
},
{
"description": "Core JavaScript standards: ES modules, modern patterns, DOM trust boundaries and XSS prevention, code quality",
"skill": "pm-dev-frontend:javascript"
}
]
}
},
"skills_by_profile_reasoning": "Composed by hand rather than by add-domain because the domain detector keys off build_systems=maven and therefore offers only java, java-cui and general-dev for this module, while every file it owns (SPA app.js, Playwright specs, fixtures, utils, playwright.config.js, eslint.config.js) is JavaScript and it carries no Java at all. general-dev supplies the cross-cutting defaults; the pm-dev-frontend skills supply the surface that actually applies; persona-integration-tester is included under module_testing because the tests here are browser end-to-end specs, not unit tests, so jest-testing does not apply.",
"tips": []
}
100 changes: 100 additions & 0 deletions .plan/project-architecture/demo-client-npm/enriched.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"best_practices": [],
"insights": [],
"internal_dependencies": [],
"key_dependencies": [],
"key_dependencies_reasoning": "",
"key_packages": {},
"purpose": "e2e-tests",
"purpose_reasoning": "the module contributes no shipped artifact; its only outputs are Playwright browser test results and lint findings",
"responsibility": "npm-side view of the same demo-client directory: owns package.json, the pinned Playwright and ESLint toolchain, the lint:strict script and the browser end-to-end specs that drive the demo SPA against the integration-tests stack in both session modes (server-side and cookie). Installed and executed only from the demo-client-maven e2e-demo profile, never from a default reactor build.",
"responsibility_reasoning": "demo-client/package.json scripts and devDependencies, playwright.config.js project matrix, tests/01-04 spec files, eslint.config.js",
"skills_by_profile": {
"implementation": {
"defaults": [
{
"description": "Core JavaScript development standards covering ES modules, modern patterns, web component patterns, DOM trust boundaries / XSS prevention, and code quality",
"skill": "pm-dev-frontend:javascript"
},
{
"description": "Language-agnostic code quality principles (SRP, CQS, complexity, error handling)",
"skill": "plan-marshall:ref-code-quality"
},
{
"description": "ESLint, Prettier, and Stylelint configuration and enforcement with systematic fixing",
"skill": "pm-dev-frontend:lint-config"
},
{
"description": "Modern CSS standards covering essentials, responsive design, quality practices, and tooling",
"skill": "pm-dev-frontend:css"
},
{
"description": "Foundational agent behavior rules (user interaction, tool usage, research, dependency management)",
"skill": "plan-marshall:persona-plan-marshall-agent"
},
{
"description": "marshalld build-server consumption client (submit/wait/ping/preflight) \u2014 build-dispatch routes builds through the daemon when the project is registered, else falls back in-process",
"skill": "plan-marshall:build-server-client"
}
]
},
"module_testing": {
"defaults": [
{
"description": "Core JavaScript development standards covering ES modules, modern patterns, web component patterns, DOM trust boundaries / XSS prevention, and code quality",
"skill": "pm-dev-frontend:javascript"
},
{
"description": "Language-agnostic code quality principles (SRP, CQS, complexity, error handling)",
"skill": "plan-marshall:ref-code-quality"
},
{
"description": "Language-agnostic testing methodology (AAA, coverage, reliability, determinism)",
"skill": "plan-marshall:persona-module-tester"
},
{
"description": "JavaScript unit testing with Jest, DOM testing, mocking, async patterns",
"skill": "pm-dev-frontend:jest-testing"
},
{
"description": "Foundational agent behavior rules (user interaction, tool usage, research, dependency management)",
"skill": "plan-marshall:persona-plan-marshall-agent"
},
{
"description": "marshalld build-server consumption client (submit/wait/ping/preflight) \u2014 test-run dispatch routes through the daemon when the project is registered, else falls back in-process",
"skill": "plan-marshall:build-server-client"
}
]
},
"quality": {
"defaults": [
{
"description": "Foundational agent behavior rules (user interaction, tool usage, research, dependency management)",
"skill": "plan-marshall:persona-plan-marshall-agent"
},
{
"description": "Language-agnostic code quality, refactoring, and documentation principles",
"skill": "plan-marshall:ref-code-quality"
}
]
},
"security": {
"defaults": [
{
"description": "Core JavaScript development standards covering ES modules, modern patterns, web component patterns, DOM trust boundaries / XSS prevention, and code quality",
"skill": "pm-dev-frontend:javascript"
},
{
"description": "Language-agnostic code quality principles (SRP, CQS, complexity, error handling)",
"skill": "plan-marshall:ref-code-quality"
},
{
"description": "JavaScript security \u2014 DOM trust boundaries, XSS sinks, sanitization, and Trusted Types",
"skill": "pm-dev-frontend:javascript-security"
}
]
}
},
"skills_by_profile_reasoning": "Every file this module owns is JavaScript: the Playwright specs, fixtures, utils, playwright.config.js and eslint.config.js. Optionals are included because lint-config (ESLint flat config behind npm run lint:strict) and css (the SPA stylesheet served from this directory) are both live surfaces.; Cross-cutting agent behaviour, code-quality and build-dispatch skills apply to every module.; The SPA renders identity and claim data into the DOM, so the JavaScript security surface (DOM trust boundaries, XSS sinks, sanitization) applies to this module under the security profile.",
"tips": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
* so the post-login redirect is never an open redirect. The engine authorization is reached through
* the {@link AuthorizationInitiation} seam, keeping the flow decoupled from the confidential-client
* wiring (discovery metadata) and unit-testable without a live IdP.
* <p>
* <strong>Response mode.</strong> The authorization URL the seam yields carries
* {@code response_mode=query} — see {@link QueryResponseModeAuthorizationRequestBuilder}, which the
* runtime wires into the engine. That is what makes the later callback a top-level GET navigation,
* the only shape on which the browser sends the {@code SameSite=Lax} binding cookie this flow sets
* here. The mode is therefore not incidental to the binding cookie — the two are one design.
*
* @author API Sheriff Team
* @since 1.0
Expand Down
Loading
Loading