{"schemaVersion":1,"format":"demo-district-acceptance-catalog","note":"Acceptance plans, not a Veriflow workflow-schema import. Start each scenario with POST /lab/api {action:start, scenario:id} in the same browser context before following its steps. Test runs use isolated PostgreSQL data. The sample credential is public synthetic data only.","demoAccount":{"email":"alex@example.test","password":"District123!","name":"Alex Sample"},"scenarios":[{"id":"hris-auth","title":"HRIS sign-in: correct and incorrect password","area":"HRIS","path":"/hris/login","setup":"A fresh company with six public sample accounts. Signed out.","steps":["Use alex@people.test with an incorrect password of at least eight characters; expect an error and no session.","Sign in with the displayed sample password; expect Alex's requests.","Sign out, then sign in as Maya and Harper to verify their separate manager and HR views."],"expected":"Invalid credentials cannot open a workspace. Correct credentials open the matching role only. A fresh run resets failed-login counters without changing the normal company."},{"id":"hris-journey","title":"Employee → manager → HR","area":"HRIS","path":"/hris/login","setup":"Six accounts across three roles and two teams. Three requests in different states.","steps":["Sign in as Alex and submit Annual leave on a free working day.","Sign out, sign in as Maya, open Approvals and approve the new request.","Sign out, sign in as Harper, inspect Alex's balances and Activity."],"expected":"The employee sees only their own requests. The manager sees only their team. Pending days move to approved days once; available balance stays consistent. HR sees the approval and audit actor."},{"id":"hris-reject","title":"Reject, explain, resubmit","area":"HRIS","path":"/hris/approvals","setup":"Signed in as Maya, the Product manager. Alex has a pending one-day request.","steps":["Open Alex's pending request, choose Reject and try an empty note.","Enter a reason of at least 10 characters and save.","Sign in as Alex; inspect the rejection, balance, and submit another request on those dates."],"expected":"Missing rejection reason is rejected. Rejection releases the reserved day. A new request can reuse the released dates; the original audit trail remains."},{"id":"hris-permissions","title":"Permission checks beyond hidden buttons","area":"HRIS","path":"/hris/requests","setup":"Signed in as Alex, an employee. Nina belongs to a different team.","steps":["Visit /hris/approvals and /hris/balances directly.","Attempt decide and adjust-balance through /hris/api as Alex.","Sign in as Maya and attempt a decision on Nina's LV-SEED-003."],"expected":"Restricted pages show access denied. Employee mutations return 403; a manager accessing another team's record receives 404. No status, balance or audit entry changes."},{"id":"hris-balance","title":"Reserved days consume the allowance","area":"HRIS","path":"/hris/new","setup":"Signed in as Alex with a one-day Annual allowance already reserved by the seeded request.","steps":["Submit another one-day Annual request on non-overlapping dates.","Cancel the seeded pending request, then submit again.","Sign in as HR and try to reduce the allowance below committed days."],"expected":"The first submission is rejected for insufficient balance. Cancelling releases the day. HR cannot set allowance below approved plus pending days."},{"id":"hris-conflict","title":"Two managers' tabs race a decision","area":"HRIS","path":"/hris/approvals","setup":"Signed in as Maya with Alex's pending request at version 1.","steps":["Open the same request in two tabs.","Approve in one; try rejecting the old version from the other tab or API.","Inspect balances and Activity."],"expected":"Exactly one transition is accepted. Stale decisions return 409. Balance and audit history reflect one decision only."},{"id":"hris-filters","title":"HR filters, sorting and pagination","area":"HRIS","path":"/hris/requests","setup":"Signed in as HR with ten requests across two teams, including seven extra half-day requests.","steps":["Go to page two, then filter to Operations and Pending.","Search an impossible name, clear filters and sort by leave start date.","Open a record, use Back, then refresh."],"expected":"Filters intersect and reset pagination. Empty results are explicit. Back and refresh preserve the URL filters and ordering. Page controls are valid at both ends."},{"id":"hris-retry","title":"Leave request saved, response interrupted","area":"HRIS","path":"/hris/new","setup":"Signed in as Alex. The next leave submission commits, then returns a simulated 503.","steps":["Submit a valid request on a free working day.","Retry the unchanged form after the error.","Inspect requests, balances and Activity."],"expected":"Only one request, one reserved amount and one submission audit entry exist.","fault":{"api":"hris","action":"submit","mode":"lost"}},{"id":"hris-expiry","title":"Employee session expires mid-form","area":"HRIS","path":"/hris/new","setup":"Signed in as Alex. The session expires immediately before submitting leave.","steps":["Complete and submit a valid request.","Sign in again and inspect requests and balances."],"expected":"The expired session receives 401, the sign-in form appears, and no request or balance change is saved.","fault":{"api":"hris","action":"submit","mode":"expire"}},{"id":"login","title":"Sign in, sign out, come back","area":"Authentication","path":"/demo/login","setup":"A registered account with a saved notebook. Signed out.","steps":["Sign in with the sample account below.","Open Saved, sign out, then use Back and reload Account.","Sign in again and open Saved."],"expected":"Favorites return after login. Account details disappear after logout, including in another tab when it regains focus."},{"id":"guest-merge","title":"Guest bag meets an existing account","area":"Authentication","path":"/demo/login","setup":"The account has 2 Sand notebooks; the guest has 1. Both saved the same notebook.","steps":["Sign in with the sample account.","Open Your bag and Saved.","Sign out, sign in again and check both lists."],"expected":"One notebook line with quantity 3 and one favorite. Logging in again does not merge the guest bag twice."},{"id":"login-lockout","title":"Wrong password and lockout","area":"Authentication","path":"/demo/login","setup":"The sample account has four failed sign-in attempts.","steps":["Submit a wrong password of at least 8 characters.","Then submit the correct sample password."],"expected":"The fifth wrong attempt says email or password is incorrect. The next attempt is rate limited for 15 minutes. Starting this scenario again resets the counter."},{"id":"session-expired","title":"Session expires while editing","area":"Authentication","path":"/demo/account","setup":"Signed in. The session expires immediately before the next profile save.","steps":["Edit Full name, then Save details.","Reload the page, sign in again and inspect the name."],"expected":"Save is rejected with a sign-in message. The original name remains; the expired session cannot change the account.","fault":{"api":"store","action":"profile","mode":"expire"}},{"id":"filters","title":"Combined filters and browser history","area":"Search & filters","path":"/demo/sites/hireline","setup":"Ten jobs, including multiple teams, job types and remote roles.","steps":["Combine Team, Job type and Remote only.","Search for an impossible term, then clear the filters.","Set filters again, open a result, use Back and reload."],"expected":"Every result matches every active filter. Zero results is clear. Clear filters resets all controls. URL, reload and Back preserve the same filtered list."},{"id":"empty-workspace","title":"A truly empty workspace","area":"Boundaries","path":"/demo/sites/daybreak","setup":"Tasks and support tickets are empty.","steps":["Inspect board counts, then open All tasks.","Create a task and return to the board.","Open Careloop and create its first request."],"expected":"Counts show zero without NaN. Empty states have working creation links. New records appear and persist after reload."},{"id":"full-workspace","title":"At the record limit","area":"Boundaries","path":"/demo/sites/daybreak/new","setup":"200 tasks already exist, the workspace limit.","steps":["Try to create another valid task.","Delete one task, then try again."],"expected":"The extra task is rejected without changing the count. Deleting one creates room for exactly one new task."},{"id":"unicode","title":"Unicode, long text and literal markup","area":"Search & filters","path":"/demo/sites/daybreak/list","setup":"Tasks include accented text, Arabic, Japanese, emoji and literal HTML-like text.","steps":["Search for café, then Japanese text 東京.","Inspect and edit the task containing <img text.","Reload and inspect the saved description."],"expected":"Search handles case and surrounding whitespace. User text remains literal text and never creates executable HTML. Long text stays within the layout."},{"id":"last-item","title":"The last notebook","area":"Commerce","path":"/demo/checkout","setup":"One notebook remains in inventory and one is in the bag.","steps":["Complete checkout with valid sample details.","Open the notebook product, then cancel the order.","Cancel again through the API using the same order ID."],"expected":"Checkout consumes the last item. Cancellation restores exactly one item; repeating cancellation never increases stock twice."},{"id":"booking-conflict","title":"Overlapping and adjacent stays","area":"Bookings","path":"/demo/sites/haven/bookings","setup":"Bamboo House is booked from three days from today through five days from today.","steps":["Note the seeded booking dates.","Try an overlapping Bamboo House stay.","Try a stay starting on the seeded checkout date."],"expected":"Overlap is rejected. Back-to-back stays are allowed. Cancelling the original frees its dates."},{"id":"last-seat","title":"One ticket, two contenders","area":"Bookings","path":"/demo/sites/gather/events/clay-club","setup":"Seven of eight Clay Club tickets are reserved in this test workspace.","steps":["Open this event in two tabs of this browser.","Reserve the last ticket in each tab.","Cancel the successful reservation and try again."],"expected":"Only one additional ticket can be reserved. Stale tabs cannot oversell. Cancellation frees one place."},{"id":"stale-edit","title":"Two tabs edit the same task","area":"Concurrency","path":"/demo/sites/daybreak","setup":"The normal seeded project board.","steps":["Open the same task in two tabs.","Save a changed title in the first tab.","Save the older form in the second tab."],"expected":"The stale form receives a conflict message. Reloading shows the first tab's change; it is never silently overwritten."},{"id":"slow-save","title":"Slow save and double click","area":"Network recovery","path":"/demo/sites/daybreak/new","setup":"The next task save takes 1.5 seconds before processing.","steps":["Fill a valid task and rapidly submit twice.","Inspect the disabled busy control and wait for completion."],"expected":"Exactly one task is created. Pending state prevents a second submission and clears when the response arrives.","fault":{"api":"workspace","action":"task-save","mode":"slow"}},{"id":"retry-save","title":"Server error before saving","area":"Network recovery","path":"/demo/sites/daybreak/new","setup":"The next task save returns 503 before changing the database.","steps":["Fill and submit a task.","Inspect the error and unchanged form values.","Submit the same form again."],"expected":"The first save changes nothing. The retry creates one task, retains the entered values and clears the error.","fault":{"api":"workspace","action":"task-save","mode":"unavailable"}},{"id":"lost-response","title":"Save succeeds, response is lost","area":"Network recovery","path":"/demo/sites/daybreak/new","setup":"The next task save commits, then returns a simulated 503 instead of its result.","steps":["Fill and submit a task.","Retry the unchanged form after the error.","Count matching tasks and reload."],"expected":"The retry reuses its request ID. One task exists, even though the first response reported an error.","fault":{"api":"workspace","action":"task-save","mode":"lost"}},{"id":"checkout-lost","title":"Order commits before a lost response","area":"Network recovery","path":"/demo/checkout","setup":"One notebook is in the bag. The next checkout commits, then returns a simulated 503.","steps":["Enter sample customer details and place the order.","Retry the unchanged checkout after the error.","Inspect order history and inventory."],"expected":"The same order is returned. History contains one order and inventory decreases only once.","fault":{"api":"store","action":"checkout","mode":"lost"}},{"id":"rate-limit","title":"Temporary HTTP rate limit","area":"Network recovery","path":"/demo/login","setup":"The next sign-in returns 429 with Retry-After: 1.","steps":["Sign in with the sample account.","Read the error, wait one second and retry."],"expected":"The first response changes no account state. A retry signs in normally and the error clears.","fault":{"api":"store","action":"login","mode":"rate"}},{"id":"load-error","title":"Initial page data fails","area":"Network recovery","path":"/demo/sites/daybreak","setup":"The next workspace bootstrap returns 503.","steps":["Open the site and inspect its error state.","Use Retry loading, without re-entering the URL."],"expected":"A visible error and keyboard-accessible retry are offered. Retrying displays the seeded board.","fault":{"api":"workspace","action":"bootstrap","mode":"unavailable"}},{"id":"coupon-pass","title":"Known passing discount","area":"Detector controls","path":"/demo?bug=0","setup":"A 100000 IDR basket with the correct discount implementation.","steps":["Enter SAVE10 in coupon-input.","Click apply-coupon.","Assert order-total has exact text 90000."],"expected":"The business assertion passes: total is 90000."},{"id":"coupon-fail","title":"Known failing discount","area":"Detector controls","path":"/demo?bug=1","setup":"A 100000 IDR basket with an intentional discount defect.","steps":["Enter SAVE10 in coupon-input.","Click apply-coupon.","Assert order-total has exact text 90000."],"expected":"A correct test runner reports a FAILED assertion: expected 90000, actual 100000. The defect is intentional."}],"workflows":[{"id":"HRIS-01","area":"HRIS","scenario":"hris-journey","title":"Three-role approval journey","steps":"Employee submits, manager approves, HR checks the balance and audit trail.","expected":"Pending → Approved; one reserved amount becomes used once. Each role sees only allowed data."},{"id":"HRIS-02","area":"HRIS","scenario":"hris-reject","title":"Rejection requires context","steps":"Reject with a blank or short reason, then a valid explanation.","expected":"Invalid reasons do not mutate state. Valid rejection releases days and is visible to the employee."},{"id":"HRIS-03","area":"HRIS","scenario":"hris-permissions","title":"API role enforcement","steps":"As employee POST decide/adjust-balance, including a forged role field.","expected":"403; roles come from the authenticated server session, never submitted fields."},{"id":"HRIS-04","area":"HRIS","scenario":"hris-permissions","title":"Manager team isolation","steps":"Maya attempts to approve Nina's request or reads Operations data.","expected":"No cross-team data is returned; the attempted decision returns 404."},{"id":"HRIS-05","area":"HRIS","scenario":"hris-permissions","title":"Employee ownership","steps":"Alex attempts to cancel Sam's request or forge employeeId on submission.","expected":"Cancellation is denied; a submission always belongs to the signed-in employee."},{"id":"HRIS-06","area":"HRIS","scenario":"hris-balance","title":"Balance reservation","steps":"Submit more days than available, counting Pending requests too.","expected":"409; no request or audit row and no negative balance."},{"id":"HRIS-07","area":"HRIS","scenario":"hris-balance","title":"Cancellation restores days","steps":"Cancel Pending and Approved future requests, then repeat.","expected":"Days are released once. Repeating the same request ID is idempotent; a new invalid transition gets 409."},{"id":"HRIS-08","area":"HRIS","scenario":"hris-journey","title":"Overlap across leave types","steps":"Submit Sick leave overlapping Pending Annual leave.","expected":"Overlap is rejected across types and pending/approved states."},{"id":"HRIS-09","area":"HRIS","scenario":"hris-journey","title":"Working-day calculation","steps":"Submit Friday through Monday, then weekend-only dates.","expected":"Friday–Monday counts two days; weekend-only is rejected."},{"id":"HRIS-10","area":"HRIS","scenario":"hris-journey","title":"Half-day boundary","steps":"Submit one weekday as Half day, then a multi-day Half day.","expected":"Single weekday reserves 0.5; multi-day half leave is rejected."},{"id":"HRIS-11","area":"HRIS","scenario":"hris-journey","title":"Invalid dates","steps":"Submit reversed, past, impossible, over-30-day and over-one-year dates.","expected":"Validation errors with no mutation, even when HTML validation is bypassed."},{"id":"HRIS-12","area":"HRIS","scenario":"hris-conflict","title":"Concurrent decisions","steps":"Send approve and reject for version 1 concurrently.","expected":"One succeeds, one gets 409; exactly one decision audit entry."},{"id":"HRIS-13","area":"HRIS","scenario":"hris-conflict","title":"Terminal status transitions","steps":"Try approving Rejected, Cancelled or already Approved requests.","expected":"409; terminal requests cannot return to Pending or change decision."},{"id":"HRIS-14","area":"HRIS","scenario":"hris-balance","title":"HR adjustment guard","steps":"Reduce allowance below pending plus approved, then set a valid amount with a reason.","expected":"Invalid adjustment is rejected; valid change is versioned and audited."},{"id":"HRIS-15","area":"HRIS","scenario":"hris-balance","title":"Stale HR balance edit","steps":"Submit two adjustments with the same employee version.","expected":"Second gets 409; the first adjustment is preserved."},{"id":"HRIS-16","area":"HRIS","scenario":"hris-filters","title":"Filter and pagination interaction","steps":"Filter while on page 2, clear and sort, use Back and reload.","expected":"Page resets on filter changes; count, rows and URL stay consistent."},{"id":"HRIS-17","area":"HRIS","scenario":"hris-retry","title":"Ambiguous submission retry","steps":"Retry an unchanged submission after its response is interrupted.","expected":"Same record ID; one reservation and one audit entry."},{"id":"HRIS-18","area":"HRIS","scenario":"hris-expiry","title":"Expired authorization","steps":"Submit after session expiry, then sign in again.","expected":"401; no request is created and no balance is consumed."},{"id":"HRIS-19","area":"HRIS","scenario":"hris-journey","title":"Role switch and logout","steps":"Sign out as employee, sign in as manager then HR; replay old session tokens.","expected":"Session tokens rotate and old tokens cannot access private data."},{"id":"HRIS-20","area":"HRIS","scenario":"hris-journey","title":"Audit integrity","steps":"Inspect actor, target, old/new decision and timestamp after submit, decide, cancel and balance change.","expected":"Each successful operation records one immutable audit event; rejected operations record none."},{"id":"AUTH-01","area":"AUTH","scenario":"login","title":"Email normalization","steps":"Sign in with ALEX@EXAMPLE.TEST; repeat with surrounding spaces through the API.","expected":"The same account is used, with no duplicate user."},{"id":"AUTH-02","area":"AUTH","scenario":"login","title":"Wrong password vs unknown user","steps":"Try a wrong password, then an unknown email.","expected":"Both return the same generic error and no account data."},{"id":"AUTH-03","area":"AUTH","scenario":"login","title":"Duplicate registration","steps":"Register the sample email again using different capitalization.","expected":"409 conflict; existing account and password are unchanged."},{"id":"AUTH-04","area":"AUTH","scenario":"login","title":"Password confirmation","steps":"Register with mismatched passwords; correct the confirmation.","expected":"Mismatch blocks submission; correction allows creation."},{"id":"AUTH-05","area":"AUTH","scenario":"login","title":"Password bounds","steps":"Submit passwords of 7, 8, 128 and 129 characters via the API.","expected":"Only 8 through 128 characters are accepted; whitespace in passwords is significant."},{"id":"AUTH-06","area":"AUTH","scenario":"login-lockout","title":"Persistent lockout","steps":"Trigger lockout, reload and try the correct password.","expected":"Reload does not reset the 15-minute rate limit."},{"id":"AUTH-07","area":"AUTH","scenario":"guest-merge","title":"Guest favorites and cart merge","steps":"Sign in twice, with a logout between attempts.","expected":"Guest cart merges once and saved IDs remain unique."},{"id":"AUTH-08","area":"AUTH","scenario":"session-expired","title":"Expired session write","steps":"Save an edited profile after expiry.","expected":"401; stored profile is unchanged."},{"id":"AUTH-09","area":"AUTH","scenario":"login","title":"Logout across tabs","steps":"Open Account in two tabs, sign out in one, focus the other.","expected":"The other tab refreshes its session and hides private profile data."},{"id":"AUTH-10","area":"AUTH","scenario":"login","title":"Session rotation","steps":"Inspect HttpOnly cookies before and after sign-in and sign-out; replay the old token via API.","expected":"Tokens rotate; the old token cannot read the account."},{"id":"AUTH-11","area":"AUTH","scenario":"login","title":"Account isolation","steps":"Use two browser contexts and try to cancel the other user's order ID.","expected":"404 and no change to another user's order."},{"id":"FILTER-01","area":"FILTER","scenario":"filters","title":"Filter intersection","steps":"Combine a team, job type, remote flag and search text.","expected":"Results match all active constraints, not just the last one."},{"id":"FILTER-02","area":"FILTER","scenario":"filters","title":"Zero results and recovery","steps":"Search an impossible term, then clear all filters.","expected":"Zero state is explicit; all ten jobs return."},{"id":"FILTER-03","area":"FILTER","scenario":"filters","title":"Back and refresh","steps":"Filter, open a job, use Back, then reload.","expected":"Filter values and result count agree with the URL."},{"id":"FILTER-04","area":"FILTER","scenario":"unicode","title":"Case and whitespace","steps":"Search CAFÉ and then padded café; search 東京.","expected":"Case and outer whitespace do not change matching; Unicode works."},{"id":"FILTER-05","area":"FILTER","scenario":"filters","title":"Literal special characters","steps":"Search for .* [ ] % and an apostrophe.","expected":"Input is literal text, never a regex or query expression."},{"id":"FILTER-06","area":"FILTER","scenario":"filters","title":"Invalid URL filters","steps":"Load a list with an unknown enum, invalid boolean and nonnumeric guest count.","expected":"Invalid values fall back to valid defaults; controls and results agree."},{"id":"FILTER-07","area":"FILTER","scenario":"filters","title":"Sort after filtering","steps":"At Haven, choose Ubud and sort price low then high.","expected":"Only Ubud stays remain and prices are correctly ordered."},{"id":"FILTER-08","area":"FILTER","scenario":"filters","title":"Saved jobs plus filters","steps":"Save two jobs; combine filters on Saved jobs; remove one.","expected":"Only saved matching jobs appear; count updates after removal."},{"id":"FILTER-09","area":"FILTER","scenario":"filters","title":"Dietary intersection","steps":"At Table & Thyme combine Bowls, vegan and text search.","expected":"Only matching vegan bowls remain; clear resets the checkbox too."},{"id":"FORM-01","area":"FORM","scenario":"empty-workspace","title":"Whitespace-only input","steps":"Create a task or ticket using only spaces in required text.","expected":"Server validation rejects it even if HTML validation is bypassed."},{"id":"FORM-02","area":"FORM","scenario":"unicode","title":"Long values","steps":"Try a 120-character task title and then 121 through the API.","expected":"120 is accepted; 121 is rejected without partial mutation."},{"id":"FORM-03","area":"FORM","scenario":"unicode","title":"Markup remains text","steps":"Save literal <img src=x onerror=alert(1)> in a task description.","expected":"The exact text is displayed; no image or script executes."},{"id":"FORM-04","area":"FORM","scenario":"filters","title":"Unsafe portfolio URL","steps":"Apply to a job using javascript: or data: as the portfolio URL.","expected":"The server accepts only http: or https: URLs."},{"id":"FORM-05","area":"FORM","scenario":"retry-save","title":"Values survive failed save","steps":"Enter multiple fields, trigger 503, then inspect and retry.","expected":"Entered values remain; error clears on success."},{"id":"FORM-06","area":"FORM","scenario":"empty-workspace","title":"Enter-key submit","steps":"Complete a form using Tab and submit with Enter.","expected":"Exactly one record is created with an accessible result."},{"id":"CART-01","area":"CART","scenario":"last-item","title":"Stock boundary","steps":"Request quantity zero, negative, fractional, string, 1 and above stock.","expected":"Only valid integer quantities within stock are accepted."},{"id":"CART-02","area":"CART","scenario":"last-item","title":"Aggregate stock across colors","steps":"Add the same product in different colors beyond combined availability.","expected":"Stock is enforced across variants; failed additions change nothing."},{"id":"CART-03","area":"CART","scenario":"coupon-pass","title":"Coupon normalization and repeat","steps":"Apply padded lowercase save10 twice.","expected":"10% applies once and the total remains 90000."},{"id":"CART-04","area":"CART","scenario":"coupon-pass","title":"Invalid coupon after valid","steps":"Apply SAVE10, then an invalid code.","expected":"Error appears; the accepted coupon and total remain consistent."},{"id":"CART-05","area":"CART","scenario":"last-item","title":"Empty bag coupon","steps":"Remove the final line, then apply a coupon via API.","expected":"Coupon is cleared and the empty-bag request is rejected."},{"id":"CART-06","area":"CART","scenario":"checkout-lost","title":"Pickup vs delivery validation","steps":"Choose pickup with no address; switch to express delivery.","expected":"Pickup needs no address; delivery requires address and five-digit postal code."},{"id":"CART-07","area":"CART","scenario":"checkout-lost","title":"Server-owned prices","steps":"Submit a fake total or unit price with checkout.","expected":"The server calculates catalog prices and shipping itself."},{"id":"CART-08","area":"CART","scenario":"checkout-lost","title":"Duplicate checkout","steps":"Send the same checkout request ID twice, including concurrently.","expected":"One order and one stock deduction; retries return the same order."},{"id":"CART-09","area":"CART","scenario":"last-item","title":"Repeated cancellation","steps":"Cancel an order twice.","expected":"Stock is restored only once."},{"id":"BOOK-01","area":"BOOK","scenario":"booking-conflict","title":"Overlapping dates","steps":"Try partial overlap, identical dates and containing dates.","expected":"Every overlap with a confirmed booking is rejected."},{"id":"BOOK-02","area":"BOOK","scenario":"booking-conflict","title":"Adjacent dates","steps":"Start a new stay exactly on an existing checkout date.","expected":"Adjacent stays are accepted."},{"id":"BOOK-03","area":"BOOK","scenario":"booking-conflict","title":"Invalid date ranges","steps":"Try same-day, reversed, impossible date, 31 nights and over one year ahead.","expected":"Invalid ranges are rejected without a booking."},{"id":"BOOK-04","area":"BOOK","scenario":"booking-conflict","title":"Guest capacity","steps":"Try zero guests and one more than room capacity.","expected":"The server rejects both requests."},{"id":"BOOK-05","area":"BOOK","scenario":"last-seat","title":"Stale availability race","steps":"Reserve the final ticket from two tabs concurrently.","expected":"One succeeds, one fails; confirmed total never exceeds capacity."},{"id":"BOOK-06","area":"BOOK","scenario":"last-seat","title":"Cancel and reserve again","steps":"Cancel a confirmed reservation and repeat the cancellation.","expected":"Capacity is released once and can be reserved again."},{"id":"STATE-01","area":"STATE","scenario":"stale-edit","title":"Lost update prevention","steps":"Save two different edits based on the same task version.","expected":"First succeeds; second receives 409 and does not overwrite it."},{"id":"STATE-02","area":"STATE","scenario":"empty-workspace","title":"Refresh after create","steps":"Create a task, reload its details and the board.","expected":"The record is saved in PostgreSQL and counts agree."},{"id":"STATE-03","area":"STATE","scenario":"empty-workspace","title":"Deleted record deep link","steps":"Delete a task, then revisit its old details URL.","expected":"A useful not-found state replaces the edit form."},{"id":"STATE-04","area":"STATE","scenario":"full-workspace","title":"Record count boundary","steps":"Attempt task 201; delete one and retry.","expected":"The limit is enforced and recovering space works."},{"id":"STATE-05","area":"STATE","scenario":"filters","title":"Duplicate active application","steps":"Apply to the same job twice, then withdraw and apply again.","expected":"Active duplicate is rejected; reapplication after withdrawal works."},{"id":"STATE-06","area":"STATE","scenario":"empty-workspace","title":"Resolved ticket reply","steps":"Create and resolve a ticket, attempt reply via API, reopen and reply.","expected":"Closed reply is rejected; reopened reply persists."},{"id":"STATE-07","area":"STATE","scenario":"empty-workspace","title":"Cancellation confirmation","steps":"Dismiss a delete/cancel confirmation, then accept it.","expected":"Dismissal changes nothing; acceptance performs the change once."},{"id":"NET-01","area":"NET","scenario":"slow-save","title":"Rapid duplicate clicks","steps":"Double-click a slow submit.","expected":"One mutation, visible busy state, then enabled controls."},{"id":"NET-02","area":"NET","scenario":"retry-save","title":"503 before commit","steps":"Submit then retry unchanged.","expected":"First request changes nothing; retry creates one record."},{"id":"NET-03","area":"NET","scenario":"lost-response","title":"503 after commit","steps":"Submit then retry unchanged.","expected":"Same request ID recovers the already-created record."},{"id":"NET-04","area":"NET","scenario":"rate-limit","title":"429 retry","steps":"Inspect Retry-After and retry after one second.","expected":"A useful error appears; a later attempt succeeds."},{"id":"NET-05","area":"NET","scenario":"load-error","title":"Initial load recovery","steps":"Trigger failed bootstrap, then use Retry loading.","expected":"No infinite loading screen; seeded data appears on retry."},{"id":"API-01","area":"API","scenario":"empty-workspace","title":"Malformed request","steps":"POST malformed JSON, an array, unknown action and missing fields.","expected":"400 responses with safe messages; no partial records."},{"id":"API-02","area":"API","scenario":"empty-workspace","title":"Payload limit","steps":"POST more than 16 KiB to each business API and the lab API.","expected":"413, with no state changes."},{"id":"API-03","area":"API","scenario":"empty-workspace","title":"Cross-origin mutation","steps":"POST with an unrelated Origin or Sec-Fetch-Site: cross-site.","expected":"403 before creating or mutating any state."},{"id":"API-04","area":"API","scenario":"lost-response","title":"Reused request ID with new payload","steps":"Replay a successful request ID with changed task data.","expected":"409; the first record is unchanged."},{"id":"API-05","area":"API","scenario":"empty-workspace","title":"Workspace isolation","steps":"Read and edit record IDs from another browser context.","expected":"Other workspace data is absent and mutations return 404."},{"id":"UX-01","area":"UX","scenario":"filters","title":"Keyboard-only filters","steps":"Navigate controls with Tab and choose values with the keyboard.","expected":"Focus is visible and every filter is operable and labeled."},{"id":"UX-02","area":"UX","scenario":"unicode","title":"Mobile long content","steps":"Use a 375px viewport with long and Unicode task content.","expected":"No horizontal document overflow or clipped controls."},{"id":"UX-03","area":"UX","scenario":"retry-save","title":"Accessible error and recovery","steps":"Trigger a server error, inspect alert semantics, retry.","expected":"The error is announced and removed when the operation succeeds."},{"id":"CONTROL-01","area":"CONTROL","scenario":"coupon-fail","title":"Known bad assertion","steps":"Assert exact total 90000 after SAVE10 in the broken scenario.","expected":"A functioning test runner reports failure with actual 100000."},{"id":"CONTROL-02","area":"CONTROL","scenario":"coupon-pass","title":"Known good assertion","steps":"Run the same assertion in the fixed scenario.","expected":"The same test passes with actual 90000."},{"id":"LAB-01","area":"LAB","scenario":"login","title":"Fresh-run repeatability","steps":"Start a preset, mutate it, then start the preset again.","expected":"A new isolated run has its original seed data."},{"id":"LAB-02","area":"LAB","scenario":"login","title":"Normal data preservation","steps":"Create a normal record, run a preset, then leave the lab.","expected":"The normal browser's data and cookies are restored unchanged."}]}