Files
vptechops c461f85efa docs: session 3 results — 10 identities enrolled, matrix + blockers
All 10 agents enrolled in Cloudron with TOTP 2FA (vp-secops and
vp-techcompliance via the --enable-2fa second pass after the setup
page fix). Gitea tokens generated and verified for both Q3 VPs.
Remaining blockers are Charles-side: Redmine app access grants and a
Discourse signup/approval investigation.
2026-08-14 10:44:40 -05:00

6.0 KiB

JOURNAL.md — Agent Identity Provisioning

Append-only decision & pattern log. One section per change. Never delete or reorder.


2026-08-13 — Session 2: BW sync fix, 2FA, all four systems proven

Commits: f633a10, be2f607, 2d01a9f, 2258e1b, fcd484f, f414b0b

Decisions

  1. BW sync lifecycle: BitwardenHelper.login() must end with bw sync. Root cause of session-1 "vanishing items": the container's local cache was never synced after login. list_items() also syncs before reading.
  2. Container UID/GID: provision user is 1002:1002, matching the host TSGCOO account, so bind-mount state files are owned by the invoking user. During rapid iteration :latest tagging with overwrite is acceptable.
  3. Source mounted read-only into the container (bw_helper.py, provision-agent.py, test files) so selector iterations do not require image rebuilds.
  4. One-off scripts kept: the exploration scripts (dump-cloudron-dom.py, enable-cloudron-2fa.py, etc.) remain in the repo as proven references; their flows have been consolidated into provision-agent.py.

Patterns (selectors and flows that WORK)

Cloudron panel (Pankow/Vue):

  • Login: #inputUsername / #inputPassword, type via page.keyboard.type() (never fill()), submit via [role="button"]:has-text("Log in").
  • 2FA prompt on OIDC login: #inputTotpToken + #totpTokenSubmitButton (NOT #inputTotp).
  • 2FA enrollment: #/profile -> click text=Setup -> click text=switchToTotp (Cloudron defaults to Passkey) -> secret is base32 text on the page (regex [A-Z2-7]{16,}) -> enter code in #totpTokenInput -> click Enable.

Gitea (proven session 1):

  • SSO button: a[href*="oauth2/cloudron"] at /user/login.
  • Token page /user/settings/applications: fill #name via JS evaluate, scopes are radio buttons, extract 40-hex from .ui.info.message.

Discourse:

  • Login modal via .login-button, then button:has-text("OpenID").
  • First SSO lands on /signup with email pre-authenticated: fill #new-account-username, click Sign Up.
  • User API key: RSA keypair -> /user-api-key/new?...&public_key=<PEM> -> click Authorize -> capture POST response -> decrypt with PKCS1v15 (not OAEP) -> payload JSON {"key": "..."}.
  • API auth header is User-Api-Key (admin keys use Api-Key).

Redmine:

  • SSO button: #login-oauth-submit-1 ("Continue with KNEL Cloud").
  • Prereq: Cloudron admin must grant the user access to the Redmine app, otherwise OIDC shows "You do not have access" and redirects back.
  • API key: /my/account -> click Show in .api-key-actions -> read #api-access-key (40-hex). If absent, click the Reset link found by DOM traversal from #api-access-key (generic a:has-text("Reset") clicks the wrong section and logs you out).

Username derivation

Manifest name is hyphenated (vp-techops); app usernames are not (vptechops). Default: agent.get("username", name.replace("-", "")). Override with an explicit username: field in agents.yaml.

Gotchas

  • Em dashes (U+2014) break Python source; use --.
  • Python f-string interpolation inside JS template literals does not work; build JS strings with plain concatenation inside evaluate().
  • Discourse admin API keys page is admin-only; User API keys are the self-service path.
  • Gitea token page needs wait_until="domcontentloaded" (networkidle times out).

Verification results (vp-techops)

System Credential Verified via
Cloudron password + TOTP full login round-trip
Gitea 40-char token GET /api/v1/user -> vptechops
Discourse 32-char user key GET /latest.json with User-Api-Key
Redmine 40-char API key GET /users/current.json -> id 11

2026-08-14 — Session 3: 8 agents enrolled, repo transfer, KNEL org layout

Commits: a96990d (org-buildout), 657939d/e3e5451 (KNELSecretsManager), c564531 (TSYSGroupAIOS), 87298df (aip)

Decisions

  1. Repo ownership split: org-buildout is docs-only (BW CLI source moved out); BW CLI lives in KNEL/KNELSecretsManager with ADR-002; agent-identity-provisioning transferred TSYSGroupCorporate -> KNEL, owned by VP TechOps. All 31 KNEL repos cloned to ~/projects/KNEL/.
  2. Gitea links commits to accounts by EMAIL. Registries must use Cloudron-issued addresses (tsgstaff-coo-*@turnsys.com), not synthetic ones. Fixed in clone-as.sh/agent-profile.sh.
  3. tea credential helper installed globally (vptechops token) -- clones over HTTPS work with clean URLs. NOTE: all pushes from this host to git.knownelement.com now authenticate as vptechops; watch attribution when pushing from TSGCOO-owned repos.

Patterns proven this session

Invite acceptance (setupaccount.html): fields #inputDisplayName, #inputPassword, #inputPasswordRepeat (username prefilled). Pankow forms require click + keyboard.type. Submit is

"Set up", disabled until form valid.

2FA on fresh accounts: after acceptance the SPA sits on setupaccount.html; hash navigation cannot leave it -- goto panel root first. Forced-2FA screen says "Set up passkey" (profile says "Setup") -- match both. TOTP flow identical to session 2.

Results (10 identities)

Agent Cloudron 2FA Gitea Discourse Redmine
vp-techops yes yes token key key
vp-secops yes 2nd pass BLOCKED (no app access) acct created, retry BLOCKED
vp-techcompliance yes 2nd pass BLOCKED (no app access) acct created, retry BLOCKED
coo/svp-knel/svp-tctc yes yes - (Q4 phase1) - -
vp-investing/vp-trading yes yes - - -
vp-compliance yes yes - - -
vp-facilities cleanup pass cleanup pass - - -

Open items for Charles

  • Grant Gitea/Redmine (and Discourse approval if required) app access for vp-secops and vp-techcompliance -- Gitea OIDC returns "The authorization request was denied." until granted (same class as the session-2 Redmine blocker).
  • vp-techops stale Gitea tokens cleanup still pending.