Files
org-buildout/resume-prompt.md
T
TSYS Group COO 6c7cb6bb52 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
2026-09-07 14:51:13 -05:00

235 lines
12 KiB
Markdown

# 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