This reverts the JSDoc-comment removal that happened during merge, keeping
the function exported so ConversationTraceModal.test.ts can import it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
extractMessageText (ConversationTraceModal): MCP task/task format,
params.message.parts, result.parts/root.text, plain string result,
priority order, error resilience.
providerIdForModel (MissingKeysModal): model match, no match,
whitespace trimming, undefined models, no required_env, multi-env sort.
Also exports extractMessageText from ConversationTraceModal for testing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
internal#189: replaces the OR-gate ("≥1 approver from eligible teams")
with an AND-gate ("all required clauses must each have ≥1 approver").
New TIER_EXPR map (single source of truth at top of script):
tier:low → engineers,managers,ceo (OR, same as before)
tier:medium → managers AND engineers AND qa???,security??? (AND)
tier:high → ceo (single-team, framework wired for future AND)
"???" suffix: teams not yet created in Gitea (qa, security). The
expression always fails for these until the teams are created and the
markers are removed. The clear error message guides ops to create them.
Expression syntax documented at top of script. Clause-level pass/fail is
annotated in the notice/error lines so PR authors can see exactly which
gate is missing without SOP_DEBUG=1.
BURN-IN (internal#189 Phase 1): continue-on-error: true on the job
prevents AND-composition from blocking PRs during the 7-day window.
Remove after 2026-05-17 per the workflow BURN-IN NOTE comment.
SOP_LEGACY_CHECK=1 env var: forces OR-gate for individual runs,
enabling a grace window for PRs in-flight at deploy time.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SettingsButton: gear button render, aria-expanded, active class toggle,
openPanel/closePanel calls, forwardRef, Radix Tooltip mock.
TopBar: header render, canvas name display, "+ New Agent" button,
SettingsButton integration, logo aria-hidden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause:
Dockerfile.tenant chowns /canvas /platform /memory-plugin /migrations
to canvas:canvas (line ~119) but not /org-templates. The image is
built as root, COPY-ed templates inherit root:root 0755. The platform
binary then runs as the canvas user (uid 1000) because of the USER
directive on line ~124, so when the !external resolver
(org_external.go, internal#77 / task #222) tries
os.MkdirAll("/org-templates/<tmpl>/.external-cache/<repo>") on first
import, mkdir(2) returns EACCES and the import handler returns 400
"org template expansion failed" (org.go:592). The user-facing error
is generic; only the server log carries:
Org import: refusing import: !include expansion failed:
!external at line 156: fetch git.moleculesai.app/molecule-ai/molecule-dev-department@v1.0.0:
mkdir cache root: mkdir /org-templates/molecule-dev/.external-cache: permission denied
Repro:
Tenant staging-cplead-2 (canary AWS 004947743811, image SHA
a93c4ce17725...). POST /org/import {"dir":"molecule-dev"} returns 400
while POST /org/import {"dir":"free-beats-all"} returns 201 — only
templates with !external trip the bug.
Fix:
Add /org-templates to the chown -R argv. One-line change. Same
ownership shape as the other writable platform-state dirs.
Why this is safe for prod:
* The platform binary already needs read access to /org-templates,
so canvas:canvas owning it doesn't widen any attack surface.
* /org-templates is image-resident, not bind-mounted; chown applies
inside the image layers and prod tenants get the fix on next
image rebuild + redeploy. Live prod tenants are unaffected until
the next deploy (no orgs currently using !external in prod —
molecule-dev consumers are all internal staging).
Verification:
After hand-applying the chown live (docker exec --user 0 ... chown -R
canvas:canvas /org-templates/molecule-dev), POST /org/import
{"dir":"molecule-dev"} returns 201 with 39 workspaces; cp-lead +
CP-BE + CP-QA + CP-Security all reach status=online within ~2 min.
Refs:
internal#77 — !external RFC (Phase 3a)
task #222 — resolver PR (introduced the unflagged-permission
dependency this fixes)
Live incident 2026-05-10 — staging-cplead-2 import failed,
chown-on-host workaround in place pending image rebuild
Issue #212: POST /workspaces with runtime=external and a URL wrote the
URL directly to the DB without validateAgentURL checking (the same check
that registry.go:324 applies to the heartbeat path). An attacker with
AdminAuth could register a workspace URL at a cloud metadata endpoint
(169.254.169.254) and exfiltrate IAM credentials when the platform
fires pre-restart drain signals.
Changes:
- workspace.go: add validateAgentURL(payload.URL) guard before the
UPDATE at line 386. 400 on unsafe URL, no DB write occurs.
- workspace_test.go: add 3 regression tests:
- TestWorkspaceCreate_ExternalURL_SSRFSafe: safe public URL → 201
- TestWorkspaceCreate_ExternalURL_SSRFMetadataBlocked: 169.254.169.254 → 400
- TestWorkspaceCreate_ExternalURL_SSRFLoopbackBlocked: 127.0.0.1 → 400
Both unsafe tests assert zero DB calls (the handler rejects before
any transaction).
Ref: issue #212.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Issue #214: documents the MOLECULE_ENV=production requirement for
staging/prod tenants to lock the /admin/workspaces/:id/test-token route.
Also adds a startup INFO log in main.go when the route is enabled, so
operators can confirm the setting in boot logs without having to probe
the endpoint directly.
Ref: issue #214.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a 4th fallback step to the token chain (cache > API > env > static)
so workspace git/gh operations survive a platform outage without requiring
a restart or platform-side fix. Addresses the 2026-05-08 incident where
every workspace lost git+gh auth simultaneously when the
/github-installation-token endpoint returned 500.
Operator places a PAT in ${CONFIGS_DIR:-/configs}/.github-token
(no root needed — /configs is agent-writable). Both _fetch_token
(git credential helper path) and _refresh_gh (gh CLI daemon path)
gain the static fallback so git and gh both recover post-incident.
Pure additive — existing cache > API > env chain is unchanged.
Empty static file is rejected (whitespace-stripped before use).
Static path never writes the cache, so the API recovers transparently
on the next refresh cycle when it comes back online.
Ref: issue #140.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
StatusBadge: all 3 status variants, aria-label, role=status, config class names.
ValidationHint: error/valid/neutral states, warning icon, valid icon, class names.
Spinner: sm/md/lg size classes, aria-hidden, motion-safe:animate-spin.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>