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

12 KiB

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 stylefeat:, 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