docs: session 3 handoff -- all flows proven, loop remaining agents

Session 2 outcome: BW sync fixed, Cloudron 2FA on, all four systems
provisioned for vp-techops with verified API keys. Flows consolidated
into provision-agent.py and documented in the provisioner JOURNAL.md.

Session 3 job: populate agents.yaml invites (plus Cloudron app-access
grants) and run the manifest loop for the remaining five agents.
This commit is contained in:
TSYS Group COO
2026-09-07 14:51:13 -05:00
parent 6c7cb6bb52
commit 7a1ae1be07
+68 -211
View File
@@ -1,234 +1,91 @@
# Resume Prompt — Agent Identity Provisioning (Session 2) # Resume Prompt — Agent Identity Provisioning (Session 3)
> **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. > **Read this entire file before doing anything.** Session 2 completed all
> four systems for vp-techops and consolidated the flows. This session loops
> the remaining agents through the manifest.
--- ---
## Who You Are ## 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. 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 First
Read `/home/TSGCOO/projects/TSYSGroupAIOS/BASELINE-PROMPT.md` and `/home/TSGCOO/projects/TSYSGroupAIOS/AGENTS.md` before starting. 1. `/home/TSGCOO/projects/TSYSGroupAIOS/BASELINE-PROMPT.md` (14 principles)
2. `/home/TSGCOO/projects/agent-identity-provisioning/STATUS.md`
3. `/home/TSGCOO/projects/agent-identity-provisioning/docs/JOURNAL.md` — all
working selectors and flows are documented here
Key rules: ## State at Session 3 Start (2026-08-13 end of session 2)
- **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.
--- **All flows are proven end-to-end for vp-techops and consolidated into
`provision-agent.py`:**
## What's Already Built and Working | System | Flow | Status |
### 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) | | Cloudron | invite -> password -> TOTP 2FA | ✅ proven |
| `agent-identity-provisioning` | `/home/TSGCOO/projects/agent-identity-provisioning` | Provisioner code, multiple commits, can't push | | Gitea | SSO -> token | ✅ proven |
| `TSYSGroupAIOS` | `/home/TSGCOO/projects/TSYSGroupAIOS` | Governance framework, clean (BW stuff was moved to org-buildout) | | Discourse | SSO -> signup -> RSA User API key | ✅ proven |
| Redmine | SSO -> API key | ✅ proven |
--- BW vault (4 items): vp-techops Cloudron (TOTP), Gitea, Discourse, Redmine.
All keys verified via their APIs.
## Current BW Vault State **Infrastructure facts:**
- Provisioner: `docker compose run --rm provision` in
`/home/TSGCOO/projects/agent-identity-provisioning`
- Source is bind-mounted read-only — no rebuild needed for .py changes
- Container user matches host TSGCOO (1002:1002); `:latest` tag reuse is
fine during iteration
- `bw_helper.py` syncs after login and before list — the session-1 state
divergence bug is fixed and covered by `test_bw_persistence.py`
- Host `bw` wrapper: `export PATH="$HOME/.local/bin:$PATH"` (not in
default PATH)
``` ## What This Session Does
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. Loop the remaining agents in `agents.yaml`:
The vp-techops Gitea token was manually stored during session 1. The token `c0388aa0504bef4e4effdb47c910ef8f6252f5bd` was verified working via Gitea API (`user=vptechops`). 1. Charles generates Cloudron invites for: vp-secops, vp-techcompliance
(Q3), coo, svp-knel, svp-tctc (Q4, phase1-only)
2. Fill `cloudron_invite:` URLs in `agents.yaml` (it is gitignored)
3. **Charles must grant each user app access in Cloudron** (Redmine lesson:
OIDC shows "You do not have access" until the admin grants the app)
4. Run per agent:
```
docker compose run --rm provision provision-agent.py --agent vp-secops
```
Q4 agents: add `--phase1-only`
5. Idempotency: BW items short-circuit (`X already exists -- skipping`),
so re-runs are safe
--- ## Prereq Checklist Before Each Agent
## Current Provisioner State - [ ] Invite URL in agents.yaml
- [ ] Cloudron app access granted (Gitea/Redmine/Discourse per manifest)
- [ ] BW vault has no stale items for that agent
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. ## Known Issues (do not re-litigate)
### Key Files 1. **bw "native" binary is a Node.js SEA** — prints Node errors on crash.
CMMC/ITAR zero-Node goal is not actually met. Needs a Charles decision.
2. **Gitea stale tokens** from session 1 may exist for vp-techops — cleanup
pending (revoke all but the stored one).
3. **Repos can't be pushed** — TSGCOO has no Gitea auth yet. All commits
are local. Wire TSGCOO's Gitea credentials when available.
4. **Discourse User API keys are user-scoped** — admin operations still
need an admin key (assign to vp-techops per STATUS.md inbox).
- `provision-agent.py` — main Playwright automation (~850 lines, lots of debug dumps) ## Mistakes to Avoid (carried forward)
- `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)
--- - Never delete BW items to fix duplicates — `update_item()` in place
- One comprehensive DOM dump per page before writing selectors
## The SSO Pattern (Proven for Gitea) (see `_debug_dump` and JOURNAL.md patterns)
- `page.keyboard.type()` not `fill()` for Vue/Pankow forms
All Cloudron-managed apps use the same SSO pattern: - `[role="button"]` not `button` for Cloudron UI
- Em dashes break Python source — use `--`
1. **Login to Cloudron panel first** (`cloudron_panel_login()`) to establish OIDC session - Redmine Reset: use the DOM-traversal targeting in `provision_redmine`,
2. **Navigate to app login page**, click SSO/OAuth button never a generic `a:has-text("Reset")` match
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