Dev acceptance walk and disposition
Part of epic Task 9387 "Operator credentials roster UX fixes at scale". PRD = Task 9385 and architecture = Task 9386, both settled. IDs AC-x.y and D0–D9 are the PRD's; A1–A22 are the architecture's.
Original work item (verbatim):
> Follow-up to Task 9378 (operator credentials page UX). That task shipped server-side sort (Created/Name/Type/Org), a "1–25 of N" count, and shows the tenant once per row. These items remain for an operator working across all tenants' credentials, up to millions of rows:
> 1. **Secrets filter scales.** `?secrets=` decrypts every row in scope on each request (`apps/api/app/routes/credentials/reads.py::_secrets_filter`) and puts every id into an `IN` list (Postgres allows at most 65535). Store a "has stored secret" boolean when the credential is written, so the filter becomes an indexed predicate.
> 2. **Paging without full counts.** Replace exact `COUNT(*)` + OFFSET with keyset (cursor) paging and a capped/estimated total ("10,000+"). Add indexes behind each sort order (`lower(name)`, `type`, org name).
> 3. **Search that finds things at scale.** Add a pg_trgm index on name/description. For operators, also match org name/slug and owner email.
> 4. **Owner picker as an async typeahead.** A native select cannot hold thousands of owners across tenants. Make it multi-select.
> 5. **Tree mode beyond 200 rows.** Build tree mode from a server-side folder listing (folders with counts, children loaded on open) instead of one 200-row page.
> 6. **Bulk actions.** Let operators select rows (per page, plus "all N matching") and then delete, change org or move them to a folder.
> 7. **Health columns and filters.** Add last used, last test result, and expired/expiring/never used, each also available as a filter.
>
> Out of scope: the three items Task 9378 shipped; credential editor pages; runner credential resolution.
>
> Done means:
> - Each item above is shipped or explicitly declined with a reason.
> - With 1M credentials in a dev DB, the roster's first page with any filter or sort loads in under 1s (EXPLAIN shows an index scan).
> - An operator can bulk-delete 500 matching credentials in one action.
## This child (architecture C7): dev acceptance walk + AC-0 disposition
A verification child. It writes no code unless it finds a defect; a defect gets a fix PR. It runs after every build child (stored facts, stamps, roster API, pager/search/health UI, owner typeahead, tree, bulk) has merged and deployed to dev.
**Node constraint: run on a node that reaches `dev.jaah.ai`.**
- Step 1 probes reach (HTTPS to the dev console).
- If the probe fails, raise ONE `AskUserQuestion` round with header `Access`, naming the node, the probe and its result. The architect's node `h4-2-gmind` could not reach dev: it resolves to private 10.60.x addresses and times out.
- If no answer clears it, deliver the AC-0 disposition (it needs no dev) and settle with the dev walk named as blocked, and why. In that case the epic's third Done-means bullet rests on the bulk child's slot API test (500 rows under 10 s); say so in the settle summary.
Steps:
1. Probe reach (above).
2. **AC-0:** read each build child's settle output with `task_read`. Write the disposition table: items 1–7 → merged PR and sha, or a written decline with its reason.
3. **AC-A walk** on `dev.jaah.ai` in a browser (Playwright): as the operator, then as a tenant Org Admin.
4. **AC-6.4 on dev.**
- Use the console's own create form only. The hub rule is that live Jaah state is changed only through the UI.
- Create 500 `generic_api_key` rows named `bulk-test-9386/<n>` in the operator's org (~15 min).
- Filter `q=bulk-test-9386`, Select all 500 matching, Delete, confirm.
- Record the time from confirm to toast.
- That delete is also the cleanup. Delete any leftover rows the same way.
5. **Visual checks** at the narrowest supported width (`docs/standards/product-testing-requirements.md`): AC-4.5, 5.6, 7.1 and 7.6, with screenshots in the report.
6. **Findings** (project instructions). Fix small UI problems found while testing in place. Anything broken, awkward or inconsistent that is a big job goes into one follow-up task, as itemized 1–2 sentence requirements.
Credentials & reach (architecture §9, verbatim):
- "AC-6.4 bulk-delete 500 + AC-A availability walk in a browser on dev (C7) | `org/console/dev-login` (operator; the admin login covers the non-admin one) on the dev control plane (`dev.jaah.ai`); tenant Org-Admin view via `me/jaah/debug/main/operator-login` ("switch between operator and several tenants"). | **Credential verified** (`credential_get` succeeded on node `h4-2-gmind`; value not recorded). **Reach deferred**: from node `h4-2-gmind`, `dev.jaah.ai` resolves to private 10.60.x addresses and HTTPS times out (as does the credential's stored `base_url` host), while `app.jaah.ai`/`api.jaah.ai` answer 200. Reason: dev is reachable only from inside its network. Mechanism: C7 carries a "run on a node that reaches dev.jaah.ai" constraint and probes first (§5)."
- "AC-4.5, 5.6, 7.1, 7.6 visual checks at phone width on dev (C7) | Same credential + environment as above; Playwright viewport per `docs/standards/product-testing-requirements.md`. | Credential verified; reach **deferred**, same reason and mechanism."
- "AC-0 disposition (C7) | Control-plane task reads (`task_read`) of the build children. | verified — the runner's own control tools."
Out of scope:
- MUST NOT re-implement or change Task 9378's sort control, "1–25 of N" count format (other than the capped variant) or once-per-row tenant display.
- MUST NOT change credential editor pages: `apps/web/src/pages/credentials/CredentialEditPage.tsx`, `AccountEditPage.tsx`, `credentialForm.tsx`, nor the Accounts page (`Accounts.tsx`).
- MUST NOT change runner credential resolution: what `routes/agent/task_secrets.py` and the credential-get path return, who may call them, or path resolution (`runner_path`).
- MUST NOT change the sealing scheme (`app/credential_crypto.py`).
- Do NOT create or change dev state except through the console UI: no CLI, SQL or raw API.
- Add no features. A fix PR stays within the defect it fixes.
Done means (PRD criteria verbatim with their oracles):
- **AC-0** Every item 1–7 is merged, or the PR/settle summary declines it with a reason. → verify: read the build tasks' settle outputs; each item maps to a merged PR or a written decline.
- **AC-A** Every capability above is ON by default with no flag, in the real deployment. → verify: on `dev.jaah.ai` as an operator in a browser: open `/credentials`; observe the "of N"/"10,000+" footer with Prev/Next; type in Search an org slug and see matching rows; type in Owner and pick two owners; switch to Tree and open a folder; pick Health → Never used; select rows and see the bulk bar with Delete/Change org/Move to folder. Then as a tenant Org Admin: observe no Change org action and search not matching other members' emails.
- **6.4** An operator bulk-deletes 500 rows matching a filter in one action, completing in under 10 s, roster refreshing to show them gone. → verify: on dev control plane in a browser: filter to a seeded set of 500 throwaway credentials, Select all 500 matching, Delete, confirm; observe success toast "Deleted 500" and the filtered roster empty. Record the elapsed time; it must be under 10 s.
- **4.5 / 5.6 / 7.1 / 7.6** Visual checks on dev at the narrowest supported width:
- owner chips wrap;
- folder names show in full, with counts right-aligned;
- health cells are readable;
- no horizontal page scroll.
Screenshots go in the report.
- The report carries the disposition table, the walk evidence, the 6.4 timing and the screenshots. Any unreachable remainder is named with its reason. (deferred — dev reach unverified from the planning node; the probe in step 1 decides)
Activity
- Draft
- Ready