docs: add resume-prompt.md for session 2 handoff
Comprehensive handoff document covering:
- What works (BW host CLI, bw_helper.py lifecycle safety)
- What's broken (container BW sync, 2FA, Redmine/Discourse SSO)
- Cloudron Pankow UI patterns (keyboard.type, role=button)
- Per-app SSO button selectors
- Mistakes from session 1 to avoid repeating
- Suggested first actions (fix BW sync before Playwright)
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -0,0 +1,234 @@
|
|||||||
|
# Resume Prompt — Agent Identity Provisioning (Session 2)
|
||||||
|
|
||||||
|
> **Read this entire file before doing anything.** This is a handoff from session 1 which got bogged down in failed Playwright selector iterations. Start fresh with a methodical approach.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Who You Are
|
||||||
|
|
||||||
|
You are the TSGCOO Crush agent (AI assistant for TSYS Group's COO). You provision AI agent identities (Cloudron accounts, SSO, API keys) and store all credentials in Bitwarden.
|
||||||
|
|
||||||
|
## Governance Rules (from TSYSGroupAIOS/BASELINE-PROMPT.md)
|
||||||
|
|
||||||
|
Read `/home/TSGCOO/projects/TSYSGroupAIOS/BASELINE-PROMPT.md` and `/home/TSGCOO/projects/TSYSGroupAIOS/AGENTS.md` before starting.
|
||||||
|
|
||||||
|
Key rules:
|
||||||
|
- **Docker for everything** — no language toolchains on the host. The ONLY exception: the `bw` wrapper at `~/.local/bin/bw` which invokes Docker.
|
||||||
|
- **No secrets on disk except BW access info** — everything in `~/.config/bw/env` (single-quoted values, perms 600).
|
||||||
|
- **Atomic commits, conventional style** — `feat:`, `fix:`, `docs:` etc. Commit often.
|
||||||
|
- **Develop on master** — push when work is complete.
|
||||||
|
- **30s timeouts for reads, 120s standard, 300s deployments.**
|
||||||
|
- **Container naming**: always `tsys-` prefix. Pin all images (no `:latest`).
|
||||||
|
- **Status.md and JOURNAL.md** must be maintained.
|
||||||
|
- **Redmine** is the system of record for work tracking (exempted during bootstrap phase).
|
||||||
|
- **The `todos` tool is OK** for tactical work within a single task. Redmine is for durable cross-agent tracking.
|
||||||
|
- **Never delete credentials.** The `BitwardenHelper` class has no `delete_item` method by design. Manual deletion via host `bw` CLI is allowed for human-directed operations only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's Already Built and Working
|
||||||
|
|
||||||
|
### Bitwarden CLI Infrastructure (SOLID — do not rewrite)
|
||||||
|
|
||||||
|
- **Host wrapper**: `~/.local/bin/bw` — invokes Docker container with full auth lifecycle
|
||||||
|
- **Container image**: `reachableceo-bw-native:2026.7.0` — debian-slim + pre-compiled bw binary
|
||||||
|
- **Dockerfile**: `/home/TSGCOO/org-buildout/docker/bw-native/Dockerfile`
|
||||||
|
- **Scripts**: `/home/TSGCOO/org-buildout/scripts/bw-cli.sh`, `bw-entrypoint.sh`, `bw-install.sh`
|
||||||
|
- **All scripts pass shellcheck** (zero warnings including info-level)
|
||||||
|
- **BW server**: `https://pwvault.turnsys.com` (self-hosted Vaultwarden)
|
||||||
|
- **BW account**: `coo@turnsys.com`
|
||||||
|
|
||||||
|
The host `bw` wrapper works reliably. Test it: `bw status`, `bw list items`, `bw generate -ulns --length 24`.
|
||||||
|
|
||||||
|
**IMPORTANT**: The "native Rust binary" from Bitwarden's GitHub releases is actually a Node.js SEA (Single Executable Application) — it's Node.js packaged as a single binary. It still prints Node.js errors on crash. The user wanted zero Node.js for CMMC/ITAR compliance. This is a **known issue** that needs to be discussed with Charles. The BW MCP server container (`kneldevstack-aimiddleware-bitwarden-mcp-crush`) is also available but needs wiring into Crush config.
|
||||||
|
|
||||||
|
### Bitwarden Credential File
|
||||||
|
|
||||||
|
`~/.config/bw/env` — all values MUST be single-quoted (password contains `$` chars):
|
||||||
|
```
|
||||||
|
BW_CLIENTID='user.98954ef9-da68-42f2-bcf8-c2343202ea60'
|
||||||
|
BW_CLIENTSECRET='WCOTwi9TbLDwzFwKXiD8pcEQy1PY3r'
|
||||||
|
BW_PASSWORD='yZ6Qw$5h$vdPL9DxEXwxCO#^m0J%dS8@'
|
||||||
|
BW_TOTP_SECRET='VLEZH74VBECT2XLHWMSTS6R4QQ2SBXCJ'
|
||||||
|
BW_SERVER='https://pwvault.turnsys.com'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repos
|
||||||
|
|
||||||
|
| Repo | Path | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| `org-buildout` | `/home/TSGCOO/org-buildout` | BW infra committed, AGPLv3, can't push (no Gitea auth) |
|
||||||
|
| `agent-identity-provisioning` | `/home/TSGCOO/projects/agent-identity-provisioning` | Provisioner code, multiple commits, can't push |
|
||||||
|
| `TSYSGroupAIOS` | `/home/TSGCOO/projects/TSYSGroupAIOS` | Governance framework, clean (BW stuff was moved to org-buildout) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current BW Vault State
|
||||||
|
|
||||||
|
```
|
||||||
|
Items: 2
|
||||||
|
30b5c3d7-... - vp-techops Cloudron - totp=no (2FA NOT enabled)
|
||||||
|
b8de991b-... - vp-techops Gitea - totp=no
|
||||||
|
```
|
||||||
|
|
||||||
|
The vp-techops Cloudron account exists with a stored password. **2FA is NOT enabled** — this is a hard blocker for CMMC compliance.
|
||||||
|
|
||||||
|
The vp-techops Gitea token was manually stored during session 1. The token `c0388aa0504bef4e4effdb47c910ef8f6252f5bd` was verified working via Gitea API (`user=vptechops`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Provisioner State
|
||||||
|
|
||||||
|
The provisioner container runs as non-root user `provision` (UID 997). It has a known **BW state sync issue** — the `bw-state/` bind mount gets out of sync between container runs. This MUST be fixed before any Playwright work matters.
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
|
||||||
|
- `provision-agent.py` — main Playwright automation (~850 lines, lots of debug dumps)
|
||||||
|
- `bw_helper.py` — BW CLI wrapper with safety guarantees (no delete, duplicate prevention, update in place)
|
||||||
|
- `agents.yaml` — manifest with real vp-techops invite link
|
||||||
|
- `Dockerfile` — Playwright base + native bw binary + python3-pip + non-root user
|
||||||
|
- `docker-compose.yml` — env_file for BW creds, bind mounts for state
|
||||||
|
- `test_bw_helper.py` — 10 credential lifecycle tests (tests 1-4 verified passing)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The SSO Pattern (Proven for Gitea)
|
||||||
|
|
||||||
|
All Cloudron-managed apps use the same SSO pattern:
|
||||||
|
|
||||||
|
1. **Login to Cloudron panel first** (`cloudron_panel_login()`) to establish OIDC session
|
||||||
|
2. **Navigate to app login page**, click SSO/OAuth button
|
||||||
|
3. **Cloudron OIDC handles auth** — may show login form (if session expired) or consent page
|
||||||
|
4. **Redirect back to app** authenticated
|
||||||
|
|
||||||
|
### Cloudron UI Technical Details
|
||||||
|
|
||||||
|
- Cloudron uses **Pankow/Vue** components
|
||||||
|
- Login form: `#inputUsername`, `#inputPassword`, `#inputTotp` (for 2FA)
|
||||||
|
- Submit button is `<div role="button">Log in</div>` — NOT a `<button>` tag
|
||||||
|
- `page.fill()` does NOT work with Vue reactive forms — must use `page.click()` + `page.keyboard.type()`
|
||||||
|
- Button click: `[role="button"]:has-text("Log in")` works; `button:has-text("Log in")` does NOT
|
||||||
|
- Profile/2FA page is at `#/profile` (NOT `#/security` which is admin-only)
|
||||||
|
- After login, Cloudron redirects to `#/apps` — use `window.location.hash = "#/profile"` to navigate
|
||||||
|
|
||||||
|
### App-Specific SSO Buttons
|
||||||
|
|
||||||
|
| App | Login URL | SSO Button Selector |
|
||||||
|
|---|---|---|
|
||||||
|
| **Gitea** | `https://git.knownelement.com/user/login` | `a[href*="oauth2/cloudron"]` (text: "Sign in with cloudron") |
|
||||||
|
| **Redmine** | `https://projects.knownelement.com/login` | `button#login-oauth-submit-1` (text: "Continue with KNEL Cloud") |
|
||||||
|
| **Discourse** | `https://community.turnsys.com/` | Click "Log In" button to open modal, then SSO button inside modal (says "with OpenID Connect" — selector unknown, needs DOM dump) |
|
||||||
|
|
||||||
|
### Gitea Token Page (`/user/settings/applications`)
|
||||||
|
|
||||||
|
- Token name input: `#name` (may not be visible to Playwright — use JS `evaluate` to fill)
|
||||||
|
- Scopes are **radio buttons** per category (not checkboxes): `write:repository`, `write:user`, `write:organization`, `write:issue`, `write:package`, `read:notification`, `read:misc`
|
||||||
|
- Generate button: `button:has-text("Generate Token")`
|
||||||
|
- Token appears in `.ui.info.message` as plain text (40-char hex string) — extract with regex `[a-f0-9]{40}`
|
||||||
|
- Use `wait_until="domcontentloaded"` (NOT `networkidle` — times out)
|
||||||
|
|
||||||
|
### Redmine API Key Page (`/my/account`)
|
||||||
|
|
||||||
|
- Needs DOM dump after successful SSO login
|
||||||
|
- Previous session never got past the Redmine login page — OIDC consent completes but redirects back to `/login`
|
||||||
|
- This may be a Redmine-side config issue (user not auto-creating from SSO)
|
||||||
|
|
||||||
|
### Discourse API Key
|
||||||
|
|
||||||
|
- Discourse API keys typically require admin to create
|
||||||
|
- Non-admin users likely cannot self-generate
|
||||||
|
- Charles may need to pre-create these
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Needs to Happen (In Order)
|
||||||
|
|
||||||
|
### Phase 0: BW Lifecycle Solid (DO THIS FIRST)
|
||||||
|
|
||||||
|
The BW state management in the provisioner container is broken. Symptoms:
|
||||||
|
- Items appear in one container run, vanish in the next
|
||||||
|
- Host `bw` and container `bw` show different vault states
|
||||||
|
- `bw sync` is called but doesn't seem to propagate
|
||||||
|
|
||||||
|
**Fix approach:**
|
||||||
|
1. Understand why the `bw-state/` bind mount doesn't persist login state across runs
|
||||||
|
2. Ensure `bw sync` is called after every login and before every read
|
||||||
|
3. Write a test that creates a dummy item, reads it back from a separate container run, verifies it persists
|
||||||
|
4. Only proceed when you can reliably: create item → read it from host CLI → read it from container → update it → verify update persists
|
||||||
|
|
||||||
|
### Phase 1: Cloudron 2FA
|
||||||
|
|
||||||
|
The TOTP secret extraction works but the confirmation step fails. The Cloudron profile page at `#/profile` has:
|
||||||
|
- A `<label>TOTP</label>` section
|
||||||
|
- A `#totpTokenInput` for entering the confirmation code
|
||||||
|
- An enable button (selector unknown — needs DOM dump of the profile page when logged in)
|
||||||
|
|
||||||
|
**The `--enable-2fa` flag exists** in provision-agent.py but the button selectors are wrong. Need to:
|
||||||
|
1. Login to Cloudron panel (keyboard.type + role=button click — this works)
|
||||||
|
2. Navigate to `#/profile`
|
||||||
|
3. DOM dump the page to find the real TOTP enable button
|
||||||
|
4. Click it, extract secret, enter TOTP code, confirm
|
||||||
|
5. Verify 2FA works by attempting a fresh login that prompts for TOTP
|
||||||
|
|
||||||
|
### Phase 2: SSO Login for All Apps
|
||||||
|
|
||||||
|
Using the established pattern:
|
||||||
|
1. `cloudron_panel_login()` first
|
||||||
|
2. Per-app SSO button click
|
||||||
|
3. OIDC handling
|
||||||
|
|
||||||
|
Gitea SSO is proven working. Redmine and Discourse need:
|
||||||
|
- **Redmine**: The "Continue with KNEL Cloud" button (`#login-oauth-submit-1`) was never actually clicked by the code. The SSO flow may work if the button is clicked properly. The OIDC consent redirect-back-to-login issue needs investigation.
|
||||||
|
- **Discourse**: Need to open login modal (click "Log In" button), then find the SSO button inside the modal. Do a DOM dump of the modal.
|
||||||
|
|
||||||
|
### Phase 3: API Key Extraction
|
||||||
|
|
||||||
|
- **Gitea**: Working (proven once). Needs cleanup of stale tokens before regenerating.
|
||||||
|
- **Redmine**: Needs DOM dump of `/my/account` after SSO login succeeds.
|
||||||
|
- **Discourse**: May need admin pre-creation. Discuss with Charles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## URLs (All Real, Operational Systems)
|
||||||
|
|
||||||
|
| System | URL |
|
||||||
|
|---|---|
|
||||||
|
| Cloudron panel | `https://my.knownelement.com` |
|
||||||
|
| Gitea | `https://git.knownelement.com` |
|
||||||
|
| Redmine | `https://projects.knownelement.com` |
|
||||||
|
| Discourse | `https://community.turnsys.com` |
|
||||||
|
| Bitwarden | `https://pwvault.turnsys.com` |
|
||||||
|
|
||||||
|
## vp-techops Account Details
|
||||||
|
|
||||||
|
- Email: `tsgstaff-coo-vptechops@turnsys.com`
|
||||||
|
- Username: `vptechops`
|
||||||
|
- Invite link: `https://my.knownelement.com/setupaccount.html?inviteToken=6aa567a43d51004155fda075badb5ffab8502f6a92fae028f90f921859b9f89f&email=tsgstaff-coo-vptechops%40turnsys.com&username=vptechops`
|
||||||
|
- Cloudron access: Granted by Charles (Gitea, Redmine, Discourse, and others)
|
||||||
|
- 2FA: NOT enabled (hard blocker)
|
||||||
|
|
||||||
|
## Mistakes from Session 1 (Do Not Repeat)
|
||||||
|
|
||||||
|
1. **Never delete BW items to "fix" duplicates** — use `update_item()` to modify in place
|
||||||
|
2. **Do ONE comprehensive DOM dump** of each page before writing selectors, not one-at-a-time
|
||||||
|
3. **Always use `timeout` on Docker commands** — 30s reads, 120s standard, 300s deployments
|
||||||
|
4. **Test BW lifecycle with dummy values first** — don't test against real credentials
|
||||||
|
5. **Commit frequently** — don't let changes pile up
|
||||||
|
6. **Don't skip 2FA** to work on other things — it's a hard compliance blocker
|
||||||
|
7. **Use `page.keyboard.type()` not `page.fill()`** for Vue/Pankow forms
|
||||||
|
8. **Use `[role="button"]` not `button`** for Cloudron Pankow UI elements
|
||||||
|
9. **Em dashes (U+2014) break Python** — use `--` instead in all source code
|
||||||
|
10. **The host bw CLI and container bw CLI share the same Vaultwarden account** — state diverges because they have separate local caches. Always `bw sync` before reads.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggested First Actions
|
||||||
|
|
||||||
|
1. Read `BASELINE-PROMPT.md` and `AGENTS.md` in TSYSGroupAIOS
|
||||||
|
2. Verify host `bw` works: `bw status`, `bw list items`
|
||||||
|
3. Fix the provisioner container BW state sync issue
|
||||||
|
4. Write a dummy BW lifecycle test (create, read, update, verify persistence)
|
||||||
|
5. Once BW is solid, do ONE DOM dump script that captures all 8 pages
|
||||||
|
6. Write all selectors from those dumps
|
||||||
|
7. Test 2FA enablement
|
||||||
|
8. Test full provisioning run
|
||||||
Reference in New Issue
Block a user