Ready to test
What a project must satisfy before a person tests it by hand on a dev
destination. The build applies this list on entry to the refine and test
phases; a project can apply it to itself at any time.
How to read an item. Each begins with a six-character identifier — quote
that, never a position in the list, because items are reordered, regrouped and
retired and the identifier is the only thing that stays put. Each ends with
where it comes from, so you can check the source rather than trust the
summary: G is the client guidelines index (with the section), M a rule
moved here from that index — the section named is where it used to sit and
where it no longer appears, because this page now owns it — R the
manifest reference, D the credential-less deploy page, X a behaviour the
platform verified in its own code, Phil a direct instruction (dated), P a
catalogue project by number, and C the conductor's field record (list, item,
date) from which the item was vetted. The conductor's numbers are given so the
two lists can be walked against each other; they are not identifiers here.
Owner. The platform writes this page. Regenerating the guidelines digest never touches it. A retired item stays on the page under Retired so a quoted identifier always resolves.
A. Manifest and configuration
- gjtz6e
wbsp.yamldeclaresvariant: <uuid>immediately beforename:, adevdestination withtype: devand atenant:, and the reserveduniversal: {}block. (G§1;R;Ctest 1–3, 2026-09-12) - y226d9 Secrets are declared by reference only. No secret value appears in
wbsp.yamlor any committed file, with the one allowed exception of the shared test-HAP values in a committed.env.<destination>.example. (G§5;Ctest 4) - s5ven7 A committed
.env.dev.exampleexists, and a git-ignored.env.devexists that runs without hand edits..gitignoreignores.env*and un-ignores!.env.*.example;.env.devis not tracked. (G§5;Ctest 5–6) - 5h372n The application's public URL and its OIDC redirect URI name the same host and port, and the callback path is one the shared test client accepts on localhost at any port:
/api/auth/callback/hap,/api/auth/callback,/login/oauth2/code/hapor/signin-oidc. Moving the port is an edit to your own env file and nothing else — do not go looking for a registration to update; there isn't one. (M§2 Running underdev;G§5, §6;Ctest 7)
B. Ports
- rcbznb The data services run on the platform-derived ports (
54NNNfor Postgres,63NNNfor Redis) — the deploy does this for you. (G§2;Ctest 8) - 9g5put Every host process you start yourself listens on a port derived from the project number (
30NNNfor a web app on 3000; a service on 8000 or 8080 folds deterministically into the IANA dynamic range[49152, 65535]), never a common default — not3000, not3001, not4000,5000,8000or8080, because those are exactly the ports every other project on the machine is also holding. The derivation in the client guidelines is the only source for these numbers; do not invent a scheme of your own and do not settle for "a free port". The platform derives ports for the data services but cannot derive one for the application, because it does not start it — the tester does, from the editor — so this derivation is yours to apply by hand, in the launch configuration and in.env.devalike. For project356that isnext dev -p 30356with the API on80356. The port is pinned in the documented start command and in.env.dev, not left to the framework's default: a front end pinned to bare3000in the README, the compose file or.env.devcollides with every other project on the developer's machine. (M§2 Running underdev, Avoiding port clashes;Ctest 9) - gud32r After sourcing the deploy-written
.envand your.env.dev, every host process still binds its derived port — not thePORT=8080the deploy writes, which describes the container contract. A second process with its own health port gets its own derived port. (M§2 Running underdev;Ctest 32) - ky54x9 After sourcing the deploy-written
.env, a framework dev server still runs in development mode. TheNODE_ENV=productionthe deploy writes is for the container and must not reachnext devor its equivalent. (Ctest 33;P61) - b779v4 A split front end and API become two origins under
dev, and only underdev— in the cloud the platform serves both from one host, so a project with an internal API often has no CORS layer, no absolute API base and no explicit front-end base URL, none of which it needed until now. Three things then break at once, and the project handles all three itself. The browser cannot reach the API: give it a CORS allowance enabled only fordev, or put a dev proxy in front (Next.jsrewrites, Viteserver.proxy) so the browser sees one origin — never a permissive rule that survives into a cloud destination. Server-side fetches have no origin to be relative to: a default base of/api/…works in a browser and fails in a host process where there is no page URL, so every server render's API call fails, usually with a message about an invalid URL rather than about the base — read an absolute API base from the env file and let the relative form be the browser-only special case. Server-side redirects land on the wrong origin: a sign-in callback or sign-out that redirects to/returns to whichever origin generated it, and on the API that is a JSON 404 that looks like a broken auth flow — build every redirect from an explicit front-end base URL supplied per destination, never from a bare path. (M§2 Running underdev— host processes, derived ports, two origins;Ctest 10)
C. Data
- 4mx8zv
wbsp deploy --destination devhas been run: both data containers are up and the deploy-written.envexists. (R;Ctest 11) - f3ydht Schema is applied by versioned migrations checked into the repository. No hand-run SQL, no out-of-band DDL. (
G§4.3;Ctest 12) - g8hwe4 The
release:command is idempotent and brings an empty database to a usable first screen with no operator step — including any row a first user needs (default workspace, tenant, role). (G§4.4;Ctest 13) - b8sf33 Demo or seed data is optional, documented, idempotent, and says which account owns it. The documented migration and seed commands run from the deploy-written
.envplus.env.devalone; if the tooling needs a connection URL, the documented command assembles it rather than asking someone to hand-write one. (G§4;Ctest 14, 34) - bfcv5s The seed and the login bypass agree on who the tester is: after a clean seed and a first bypass sign-in, the tester lands on populated data holding the stated role — not an empty organisation, and not a staff-only view. (
Ctest 35;P61) - mtfjxv Automated browser suites that drive the running dev application clean up what they create, or run against a database other than the one the tester is about to use. The tester never meets stray organisations, users or records left by a test run. (
Ctest 36)
D. Running the application
- t4m8rz Do not ask the tester to start the application until you expect it to start and run correctly. Asking is a request for someone's attention, and an application that falls over on the first page spends it for nothing — so before asking, satisfy yourself on both halves. The environment is ready: the data services are up, the deploy-written environment file and
.env.devare both present, migrations and any seed have been run against the database the application will actually open, and every value a host process needs is in a file rather than in somebody's shell history. The work is finished: every user story and task in scope is complete, every phase of development that was meant to precede this one is closed, and nothing is left half-wired behind a flag or a stub that the first click will reach. A partially built application handed to a tester produces a defect report about the part you already knew was missing, which costs the tester a session and tells you nothing. If something genuinely cannot be finished before the test, say plainly which part is absent and what it will do when reached, before the tester starts — not afterwards, in answer to the report. (Phil2026-09-21) - v7k3qd Inside a Manual Development item of the build pipeline the session never starts the application — the user does, from the VS Code launch configuration. Launching it that way is what puts it under the user's control in the editor, so they can stop and restart it from the interface for as long as they are working. A session that starts it instead takes those controls away, leaves a process the user cannot see to stop, and usually hides the very failure the hand-over was meant to surface. So when such an item begins, the session hands over and waits: it does not start the application and it does not poll it. Outside those items the session may start the application — to check a change, capture a screenshot, run a suite — on two conditions. It binds the ports derived from the project number and never a common default (9g5put). And it stops the application again before the next Manual Development item begins, so the user arrives at a clean machine and presses the launch configuration themselves rather than inheriting a process somebody else started. The configuration is written by the pipeline's Launch script for VSC item; this page does not ask the project to invent one. (
Phil2026-09-21, clarified 2026-09-26) - p5q4tg Under
devthe server and the website run as host processes — neverdocker compose upfor the application; v7k3qd says who starts them and when. The README, getting-started page and useful-commands page show the host-process form; if the project also supportscompose, each command says which destination it belongs to. A README that saysdocker compose up api worker beatunder adevdestination is describing a different destination. (M§2 Running underdev;Ctest 15) - 54jenk A run started from the VS Code launch configuration puts the application's logs and output on the console, where the user can read them in VS Code. That is the point of launching it from the editor, and a run that buries its output in a file the user has to go looking for defeats it. A run the session starts for itself — outside a Manual Development item, per v7k3qd — writes to a log file whose path the project names when it reports, so the session has something to read back. (
Ctest 16;Phil2026-09-21, clarified 2026-09-26) - dmfrec Every environment variable a host process needs comes from the project's own env files or a documented
export. Nothing depends on a value only a container used to inject. (G§5;Ctest 17) - qnxzsy The documented start commands match the project's own toolchain (pnpm versus npm, uv versus pip) and the README describes what actually runs. (
Ctest 18) - 5m8pgf A health endpoint answers, and the project says its path. (
Rhealth_check;Ctest 19)
E. Sign-in and roles
- b6k9a7 The application is configured for the shared test client (issuer, client id, its own redirect URI on its derived port) so that the other local destinations work. The HAP round trip is not exercised during
devtesting — see the bypass below — so a missing login route is noted, not a blocker. (G§5, §6;Ctest 20) - qjmj6n A user HAP has authenticated, for whom the application holds no row, is created and admitted with the ordinary default role on first sign-in. There is no "awaiting access" state that an operator must clear. (
G§6;Ctest 21) - eqea7j The application's administrator role is mapped from HAP's administrator claims, and both claims are read. On the shared test client
is_app_adminisfalsefor everyone, so underdevthe application treatsis_tenant_adminas the designation, and logs which claim admitted a user, so the difference is visible rather than mysterious.is_app_adminanswers "does this user administer this client", and on a client every project on the machine signs in through, that is not a statement about your application at all — so an admin mapping that works only whenis_app_adminis true leaves you with no administrator locally while the same code is correct in the cloud, where the application has a registration of its own. An application whose authority is entirely per workspace or per organisation says so instead. (G§6 Read the administrator claims;M§6 On a local destination the claims mean less;Ctest 22, 37) - vb2wuv No first-user-wins bootstrap and no hand-typed administrator subject where HAP is present. The explicit-declaration fallback is only for an install with no identity provider. (
G§6;Ctest 23) - wkdpe8 A user who is authenticated and then refused sees a page saying so and who to contact — never a redirect back toward sign-in. (
G§6;Ctest 24)
F. The local login bypass
- 9ckagf The project has a local-only login bypass: an environment-variable flag, off by default, honoured only on the local destinations (
dev,compose,standalone) and impossible to activate on a cloud destination — the code refuses it there, not merely the configuration; gating additionally on the application's own address being a loopback host is a good way to guarantee that. It signs the tester in as a synthetic principal with a stated role. Manual testing underdevuses it; nobody signs in through HAP during dev testing. Carrying one is not a liberty the project takes — it is required, because three steps of the build pipeline (UI fine-tuning, end-to-end testing and walkthrough screenshot capture) run unattended and cannot complete without it: a real sign-in through the shared test client ends at a one-time code sent to an email address, which is exactly the step nobody is present to complete. It does not go through the identity provider — that is the whole point. The application stays configured against the shared test client, as every local destination is; the bypass establishes a local session directly for an identity the deployment has already declared, so it cannot grant a role that deployment did not declare. None of this replaces real sign-in for a developer, which the pre-wired local env files make work immediately. (M§6 A local sign-in bypass is expected — with hard limits;Phil2026-09-05;Ctest 30) - p7hscb The bypass flag is in the committed
.env.dev.exampleand switched on in.env.dev, so someone following the project's own getting-started page lands signed in. (Ctest 37) - bswm9j With the bypass on, every protected page answers 200 as that principal. The admin surface and any customer-facing surface each have a way in, or the project says which principal reaches which; where authority is per workspace, the bypass principal holds the widest role in a populated workspace. (
Ctest 31, 37) - pjz4pr On a loopback address every sign-in cookie — session, state, PKCE verifier, CSRF, callback — carries the port as a leading segment of its name (
p30045.authjs.session-token), and keeps the framework's default names on a deployed host, which has an address to itself and where prefixed (__Secure-…) names and domain scoping behave differently. Condition the renaming on the address being loopback, not on a "development" flag. Three rules guarantee the collision it prevents: local ports are derived from the project number, so several applications run on one host at once; Next.js with a standard auth library is the preferred stack, so they write the same cookie names by default; and the session signing secret is generated per install, so none of them can open another's cookie. A browser cookie is scoped to a host, not a port —localhost:30045andlocalhost:30123share one jar — so whichever application signed in last owns the name and the others are handed a cookie they cannot decrypt. The symptom does not resemble the cause: a decryption or JWT error with a stack trace, on an application the developer may not even have been using, read and reported as a broken install. Rename every cookie in the round trip, not just the session — the state, PKCE verifier and CSRF cookies are single-named too, so the callback collides even when the session does not. Prefix, never suffix: Auth.js splits an oversized session cookie into<name>.0,<name>.1, … and reassembles by collecting every cookie whose name begins with the base name, so an appended port is indistinguishable from a chunk index —3004would swallow30045, and an application still on the plain name would collect a migrated neighbour's whole session as a fragment of its own. This is not a way to share one sign-on between applications; that is HAP's job at the identity layer. (M§6 Name your session cookies apart on a loopback address;Ctest 39)
G. Rendering
- n95wud Every primary page answers 2xx with a rendered body: no blank page, no framework error boundary, no raw JSON where a page should be, no console error that breaks rendering. Primary navigation works signed out and signed in. (
Ctest 25, 27) - s3eddp Front-end and back-end dependency versions agree with each other (a Next release with the React it requires, and so on), and the dev server log shows no error on a plain request. (
Ctest 26)
H. Looking ahead to the demo
These are not needed for a dev test, but the projects that got them wrong did so because they were first considered at the sandbox gate. Consider them now.
- 4zrr3v In sample mode (
WBSP_SAMPLE_MODE=true, injected by the platform) the application attaches every newly provisioned user to the seeded demonstration data with the role the demonstration needs; a per-workspace model otherwise shows an empty picker over a full database. The dump is taken from a clean migrate-and-seed, never from the development database. (G§1, §6;Ctest 38;P123) - ab5jvy On any HAP-backed destination an unauthenticated visitor is redirected straight into the authorization request — never to an in-app sign-in page with a button — and the sign-in start route, the OIDC callback, the health endpoint and static assets are exempt from that redirect. The in-app sign-in page is only the fallback for an install with no identity provider. (
G§6 Silent SSO only happens if your application starts it;Ctest 40)
I. Correspondence
- 6ppakn
notes/inbox/andnotes/outbox/exist and the mail skill is installed at a current revision. When asked for this checklist, the project replies one line per identifier. (G§1/notes;Ctest 28) - h7vcd3 The project changes nothing outside its own repository and sends nothing to a core project. Anything it believes is a platform matter goes in its reply, for the operator to take up. (
G§9;Ctest 29)
Retired
None yet.