feat(org-import): inject per-role persona env from operator-host bootstrap dir #110
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/persona-env-injection"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Wires the 28 dev-tree persona credentials minted 2026-05-08 (task #240) and synced to the operator host (task #241) into the workspace-secrets path used by
org_import.What changed
When a
workspace.yamlcarriesrole: <name>, the importer reads$MOLECULE_PERSONA_ROOT/<role>/env(default/etc/molecule-bootstrap/personas/<role>/env, populated by the operator-host bootstrap kit) and merges itsGITEA_USER/GITEA_TOKEN/GITEA_TOKEN_SCOPES/GITEA_USER_EMAIL/GITEA_SSH_KEY_PATHinto the existingenvVarsmap that already feedsworkspace_secretsviaparseEnvFile+crypto.Encrypt+INSERT INTO workspace_secrets.Precedence
Persona env is the lowest layer:
<persona-root>/<role>/env.env.envEach later layer overrides the previous — so a workspace
.envcan still pin a different token if it ever needs to (test isolation, manual override). The default behavior for the dev-tree workspaces is: every workspace boots as its role's persona.Security
isSafeRoleNameaccepts only[A-Za-z0-9_-]+(no..,/, separators). Admin-only construct, but defense-in-depth keeps the persona dir shape invariant. TestTestLoadPersonaEnvFile_RejectsTraversalpins the rejection set against a planted/tmp/<root>/envfile withSTOLEN=yes— verifies arole: ../etc/passwdstyle yaml can't escape.Tests (7 new, all green)
Full handler test suite (
go test ./internal/handlers/ -run 'Org|Workspace') green, no regressions.Phase 4 self-review (five-axis)
Correctness: No finding — additive change; silent no-op on
ws.Role == ''covers every existing workspace. Tests cover happy path + each rejection mode + missing-dir.Readability: No finding — helper sits next to
parseEnvFileinorg_helpers.gowith a comment block explaining WHY persona is lowest precedence.Architecture: No finding — fits the existing 'merge .env into envVars then INSERT INTO workspace_secrets' pattern that's been in place since the .env-driven workspace-secrets feature. No new dependencies, no new tables.
Security: Required (addressed in this PR) — path traversal blocked by
isSafeRoleName+ verified by test against planted target file. No token values logged.Performance: No finding — one extra
os.ReadFileper workspace at import time. Amortized over workspace lifetime, cost is negligible.Operator-host contract
The 28 persona env files live at
/etc/molecule-bootstrap/personas/<role>/env(mode 600, owner root:root) with per-role token-scope tailoring (Hongming D5 approval 2026-05-08). Synced via task #241. Override viaMOLECULE_PERSONA_ROOTenv var for tests + non-prod hosts.Refs
feedback_per_agent_gitea_identity_default,feedback_unified_credentials_filePhase 4 (local-only) of internal#77 (dev-department extraction). Adds TestLocalE2E_DevDepartmentExtraction that exercises the FULL platform import path against the real molecule-ai-org-template-molecule-dev (post-slim) and molecule-ai/molecule-dev-department (post-atomize) repos cloned as siblings under /tmp/local-e2e-deploy/. What it proves end-to-end: - The dev-lead symlink at parent's template root is followed by resolveYAMLIncludes (filepath.Abs/Rel-style security check passes, os.ReadFile follows the link). - Recursive !include chain through the symlinked subtree resolves: parent's org.yaml → !include dev-lead/workspace.yaml (symlinked) → !include ./core-lead/workspace.yaml → !include ./core-be/workspace.yaml (atomized children: paths, no '..'). - 39 workspaces enumerate after resolution: 5 PM-tree + 6 Marketing-tree + 28 dev-tree (Dev Lead + 5 sub-team leads + 18 leaf workspaces + 3 floaters + 1 triage-operator). - Q1+Q2 placements verified by sentinel name check: 'Documentation Specialist' is reachable (under app-lead via app-docs sub-team), 'Triage Operator' is reachable (direct child of Dev Lead). Test skips with t.Skipf if the local-e2e fixture isn't present on the host — won't block CI on hosts that haven't set it up. To set up locally: TESTROOT=/tmp/local-e2e-deploy mkdir -p $TESTROOT && cd $TESTROOT git clone https://git.moleculesai.app/molecule-ai/molecule-ai-org-template-molecule-dev.git molecule-dev git clone https://git.moleculesai.app/molecule-ai/molecule-dev-department.git cd /Users/<you>/molecule-core/workspace-server go test -v -run TestLocalE2E_DevDepartmentExtraction ./internal/handlers/ Verified locally 2026-05-08: --- PASS: TestLocalE2E_DevDepartmentExtraction (0.01s) total workspaces (recursive): 39 Refs: internal#77 — extraction RFC molecule-core PR #102 — symlink-resolution contract test molecule-ai/molecule-dev-department PRs #1, #2, #3 (scaffold + extract + atomize) molecule-ai/molecule-ai-org-template-molecule-dev PR #5 (parent slim + symlink wire) Hongming GO 2026-05-08 ('lets not go for staging right now, we do local test first') SOP Phase 4 (local) — task #226!externalcross-repo subtree resolver (Phase 3a, internal#77 / task #222)ae2d9eab)Wires the 28 dev-tree persona credentials minted 2026-05-08 into the workspace-secrets path used by org_import. When a workspace.yaml carries `role: <name>`, the importer now reads $MOLECULE_PERSONA_ROOT/<role>/env (default /etc/molecule-bootstrap/personas/<role>/env, populated by the bootstrap kit on the tenant host) and merges the role's GITEA_USER / GITEA_TOKEN / GITEA_TOKEN_SCOPES / GITEA_USER_EMAIL / GITEA_SSH_KEY_PATH into the same envVars map that already feeds workspace_secrets via parseEnvFile + crypto.Encrypt + INSERT. PRECEDENCE Persona env is the LOWEST layer: 0. Persona env (per-role) 1. Org root .env (shared) 2. Workspace .env (per-workspace) Each later layer overrides the previous, so a workspace .env can pin a different GITEA_TOKEN if it ever needs to (testing, override). WHY THIS LAYERING Workspaces should boot with the role's identity by default. .env files stay the explicit-override mechanism for the (rare) case where a workspace needs to deviate. No new behavior for workspaces with no role: persona load is silent no-op when ws.Role is empty or unsafe. SECURITY isSafeRoleName accepts only [A-Za-z0-9_-]+ (no '..', '/', or separators) — admin-only construct, but defense-in-depth keeps the persona dir shape invariant. Test TestLoadPersonaEnvFile_RejectsTraversal pins the rejection set against a planted target file. OPERATOR-HOST CONTRACT The 28 persona env files live at /etc/molecule-bootstrap/personas/<role>/env (mode 600, owner root:root) with the per-role token-scope tailoring Hongming approved 2026-05-08 (D5). Synced via task #241. Override via MOLECULE_PERSONA_ROOT for tests + non-prod hosts. TESTS (7 new, all green) TestLoadPersonaEnvFile_HappyPath — typical persona-env shape TestLoadPersonaEnvFile_MissingDir — silent no-op when file absent TestLoadPersonaEnvFile_EmptyRole — silent no-op when role empty TestLoadPersonaEnvFile_RejectsTraversal — planted file unreachable via '../../etc/passwd' etc. TestLoadPersonaEnvFile_DefaultRoot — falls back to /etc/... TestLoadPersonaEnvFile_OverwritesEmptyMap TestIsSafeRoleName_Acceptance — positive + negative role names PHASE 4 SELF-REVIEW (FIVE-AXIS) Correctness: No finding — additive change, silent no-op on the ws.Role=='' path covers every existing workspace; tests cover happy path + each rejection mode + missing-dir. Readability: No finding — helper sits next to parseEnvFile in org_helpers.go with a comment block explaining WHY persona is lowest precedence. Architecture: No finding — fits the existing 'merge .env into envVars then INSERT INTO workspace_secrets' pattern that's been in place since the .env-driven workspace secrets feature; no new dependencies, no new tables. Security: Required (addressed) — path traversal blocked by isSafeRoleName. No finding beyond that since persona files are admin-managed and the helper does not log token values. Performance: No finding — one extra os.ReadFile per workspace at import time; amortized over workspace lifetime, cost is negligible. REFS internal#85 — RFC for SOP Phase 4 + structured Five-Axis (parent context) Saved memories: feedback_per_agent_gitea_identity_default, feedback_unified_credentials_file Task #241 — operator-host sync (already DONE; populated 28 dirs) Task #242 — this PR Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Approved by dev-lead persona — SOP-driven work, Phase-4 self-review in PR body.