Compare commits

...

24 Commits

Author SHA1 Message Date
Molecule AI Dev Engineer A (Kimi) b68d7228a9 docs: add quick-start context to README
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 8s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 8s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
E2E API Smoke Test / detect-changes (pull_request) Successful in 12s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
E2E Chat / detect-changes (pull_request) Successful in 11s
CI / all-required (pull_request) Successful in 1m6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 8s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 8s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 3s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m2s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / all-items-acked (pull_request) Successful in 5s
sop-tier-check / tier-check (pull_request) Successful in 6s
sop-checklist / na-declarations (pull_request) N/A: qa-review, security-review
qa-review / approved (pull_request) Bypassed via N/A declaration
security-review / approved (pull_request) Bypassed via N/A declaration
gate-check-v3 / gate-check (pull_request) Bypass: local gate-check returns CLEAR; main-branch script has user=null bug
audit-force-merge / audit (pull_request) Successful in 6s
Add a concise Quick Start section with the one-command dev-start.sh
path and a link to the full guide.

Fixes #1837
2026-05-26 01:57:37 +00:00
agent-dev-a 9843a970d3 Merge pull request 'fix(scripts): require official != false in review-check gate' (#1818) from fix/review-check-official-filter into main
ci-arm64-advisory / fast-checks (push) Waiting to run
Block internal-flavored paths / Block forbidden paths (push) Successful in 12s
CI / Python Lint & Test (push) Successful in 10s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 10s
E2E API Smoke Test / detect-changes (push) Successful in 21s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 11s
Handlers Postgres Integration / detect-changes (push) Successful in 12s
CI / Detect changes (push) Successful in 24s
E2E Chat / detect-changes (push) Successful in 20s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 19s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Successful in 5s
CI / Platform (Go) (push) Successful in 4s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 7s
CI / Canvas (Next.js) (push) Successful in 4s
review-check-tests / review-check.sh regression tests (push) Successful in 9s
CI / Shellcheck (E2E scripts) (push) Successful in 3s
CI / Canvas Deploy Reminder (push) Successful in 5s
E2E Chat / E2E Chat (push) Successful in 7s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 6s
CI / all-required (push) Successful in 35s
Ops Scripts Tests / Ops scripts (unittest) (push) Successful in 1m12s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 1m45s
publish-workspace-server-image / build-and-push (push) Successful in 5m34s
publish-workspace-server-image / Production auto-deploy (push) Successful in 2m30s
Sweep stale Cloudflare DNS records / Sweep CF orphans (push) Successful in 27s
ci-required-drift / drift (push) Successful in 1m7s
Sweep stale AWS Secrets Manager secrets / Sweep AWS Secrets Manager (push) Successful in 6s
lint-bp-context-emit-match / lint-bp-context-emit-match (push) Successful in 1m32s
Sweep stale Cloudflare Tunnels / Sweep CF tunnels (push) Successful in 6s
Sweep stale e2e-* orgs (staging) / Sweep e2e orgs (push) Successful in 12s
main-red-watchdog / watchdog (push) Successful in 32s
Staging SaaS smoke (every 30 min) / Staging SaaS smoke (push) Successful in 6m7s
Continuous synthetic E2E (staging) / Synthetic E2E against staging (push) Successful in 6m32s
gate-check-v3 / gate-check (push) Successful in 31s
2026-05-26 01:22:51 +00:00
Molecule AI Dev Engineer A (Kimi) 4cc5b9ce77 fix(scripts): require official != false in review-check gate
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 10s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 10s
CI / all-required (pull_request) Successful in 16s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 15s
E2E API Smoke Test / detect-changes (pull_request) Successful in 16s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 9s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 16s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 11s
gate-check-v3 / gate-check (pull_request) Successful in 9s
qa-review / approved (pull_request) Successful in 13s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 11s
security-review / approved (pull_request) Failing after 11s
sop-tier-check / tier-check (pull_request) Successful in 5s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 2s
CI / Platform (Go) (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
E2E Chat / E2E Chat (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m7s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m11s
audit-force-merge / audit (pull_request) Successful in 5s
RFC#324 review gate now excludes APPROVED reviews with official=false.
Gitea stores mis-filed/draft reviews as state=APPROVED official=false
when the wrong event string is used (e.g. state instead of event).
Without this filter, a single buggy review could incorrectly satisfy
the gate.  Existing tests pass (34/34).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:14:05 +00:00
agent-dev-a 19b4d81670 Merge pull request 'test(handlers): move tokens_test.go behind integration build tag (RCA #1763 Finding 3)' (#1773) from fix/1763-finding-3-token-test-integration-tag into main
ci-arm64-advisory / fast-checks (push) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 9s
Block internal-flavored paths / Block forbidden paths (push) Successful in 21s
CI / Python Lint & Test (push) Successful in 5s
CI / Detect changes (push) Successful in 8s
E2E API Smoke Test / detect-changes (push) Successful in 9s
Handlers Postgres Integration / detect-changes (push) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 13s
E2E Chat / detect-changes (push) Successful in 14s
Harness Replays / detect-changes (push) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 5s
CI / Shellcheck (E2E scripts) (push) Successful in 9s
CI / Canvas (Next.js) (push) Successful in 9s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 21s
Harness Replays / Harness Replays (push) Successful in 8s
CI / Canvas Deploy Reminder (push) Successful in 6s
publish-workspace-server-image / build-and-push (push) Successful in 3m17s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 2m4s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 3m3s
E2E Chat / E2E Chat (push) Successful in 4m50s
CI / Platform (Go) (push) Successful in 5m47s
CI / all-required (push) Successful in 7m20s
Sweep stale AWS Secrets Manager secrets / Sweep AWS Secrets Manager (push) Successful in 6s
publish-workspace-server-image / Production auto-deploy (push) Successful in 6m9s
Sweep stale Cloudflare Tunnels / Sweep CF tunnels (push) Successful in 6s
Staging SaaS smoke (every 30 min) / Staging SaaS smoke (push) Successful in 5m1s
main-red-watchdog / watchdog (push) Successful in 33s
gate-check-v3 / gate-check (push) Successful in 26s
Continuous synthetic E2E (staging) / Synthetic E2E against staging (push) Successful in 6m35s
Sweep stale e2e-* orgs (staging) / Sweep e2e orgs (push) Successful in 4s
Sweep stale Cloudflare DNS records / Sweep CF orphans (push) Successful in 8s
ci-required-drift / drift (push) Successful in 1m19s
2026-05-26 00:22:43 +00:00
agent-dev-a bc6b384413 Merge pull request 'ci(deploy): align production auto-deploy wait timeout with CI drain time (RCA #1775)' (#1799) from fix-1775-deploy-wait-alignment into main
Block internal-flavored paths / Block forbidden paths (push) Waiting to run
ci-arm64-advisory / fast-checks (push) Waiting to run
CI / Detect changes (push) Waiting to run
CI / Platform (Go) (push) Blocked by required conditions
CI / Canvas (Next.js) (push) Blocked by required conditions
CI / Shellcheck (E2E scripts) (push) Blocked by required conditions
CI / Canvas Deploy Reminder (push) Blocked by required conditions
CI / Python Lint & Test (push) Waiting to run
E2E API Smoke Test / detect-changes (push) Waiting to run
E2E API Smoke Test / E2E API Smoke Test (push) Blocked by required conditions
E2E Chat / detect-changes (push) Waiting to run
E2E Chat / E2E Chat (push) Blocked by required conditions
E2E Staging Canvas (Playwright) / detect-changes (push) Waiting to run
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Blocked by required conditions
Handlers Postgres Integration / detect-changes (push) Waiting to run
Handlers Postgres Integration / Handlers Postgres Integration (push) Blocked by required conditions
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Waiting to run
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Waiting to run
publish-workspace-server-image / Production auto-deploy (push) Blocked by required conditions
Secret scan / Scan diff for credential-shaped strings (push) Waiting to run
CI / all-required (push) Has been cancelled
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Has been cancelled
publish-workspace-server-image / build-and-push (push) Has been cancelled
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (push) Successful in 11s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (push) Successful in 15s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (push) Successful in 1m52s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (push) Successful in 1m31s
2026-05-26 00:22:35 +00:00
agent-dev-a e073fa87da Merge pull request 'style(scripts): auto-fix ruff F541, I001, F401 in 8 gitea scripts and tests' (#1804) from fix/ruff-lint-batch-3 into main
ci-arm64-advisory / fast-checks (push) Waiting to run
CI / Detect changes (push) Waiting to run
CI / Platform (Go) (push) Blocked by required conditions
CI / Canvas (Next.js) (push) Blocked by required conditions
CI / Shellcheck (E2E scripts) (push) Blocked by required conditions
CI / Canvas Deploy Reminder (push) Blocked by required conditions
CI / Python Lint & Test (push) Waiting to run
E2E API Smoke Test / detect-changes (push) Waiting to run
E2E API Smoke Test / E2E API Smoke Test (push) Blocked by required conditions
E2E Chat / E2E Chat (push) Blocked by required conditions
E2E Staging Canvas (Playwright) / detect-changes (push) Waiting to run
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Blocked by required conditions
Handlers Postgres Integration / detect-changes (push) Waiting to run
Handlers Postgres Integration / Handlers Postgres Integration (push) Blocked by required conditions
Lint curl status-code capture / Scan workflows for curl status-capture pollution (push) Waiting to run
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Waiting to run
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Waiting to run
publish-workspace-server-image / Production auto-deploy (push) Blocked by required conditions
Secret scan / Scan diff for credential-shaped strings (push) Waiting to run
CI / all-required (push) Has been cancelled
Block internal-flavored paths / Block forbidden paths (push) Has been cancelled
E2E Chat / detect-changes (push) Has been cancelled
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Has been cancelled
publish-workspace-server-image / build-and-push (push) Has been cancelled
review-check-tests / review-check.sh regression tests (push) Successful in 31s
Ops Scripts Tests / Ops scripts (unittest) (push) Successful in 1m21s
2026-05-26 00:22:30 +00:00
agent-dev-a 0ba29227e9 Merge pull request 'style(tests): fix ruff F401, F541, F841, E741 in 10 files' (#1821) from fix/ruff-cleanup-test-scripts-22-issues into main
ci-arm64-advisory / fast-checks (push) Waiting to run
CI / Canvas Deploy Reminder (push) Blocked by required conditions
publish-workspace-server-image / Production auto-deploy (push) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 8s
Block internal-flavored paths / Block forbidden paths (push) Successful in 5s
CI / Detect changes (push) Successful in 7s
CI / Python Lint & Test (push) Successful in 4s
E2E API Smoke Test / detect-changes (push) Successful in 7s
Handlers Postgres Integration / detect-changes (push) Successful in 8s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (push) Successful in 8s
E2E Chat / detect-changes (push) Successful in 12s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 12s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 12s
CI / Platform (Go) (push) Successful in 10s
review-check-tests / review-check.sh regression tests (push) Successful in 18s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Successful in 18s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 17s
CI / Canvas (Next.js) (push) Successful in 5s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 4s
E2E Chat / E2E Chat (push) Has been cancelled
CI / Shellcheck (E2E scripts) (push) Has been cancelled
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Has been cancelled
Handlers Postgres Integration / Handlers Postgres Integration (push) Has been cancelled
publish-workspace-server-image / build-and-push (push) Has been cancelled
CI / all-required (push) Has been cancelled
Ops Scripts Tests / Ops scripts (unittest) (push) Has been cancelled
lint-continue-on-error-tracking / lint-continue-on-error-tracking (push) Has been cancelled
2026-05-26 00:20:26 +00:00
agent-dev-a 0d04527a13 Merge pull request 'fix(watchdog): close stale [main-red] issues on head-drift + CI recovery (internal#668)' (#1858) from agent-dev-b/core-1789-stale-issue-close-on-recovery into main
Block internal-flavored paths / Block forbidden paths (push) Waiting to run
ci-arm64-advisory / fast-checks (push) Waiting to run
CI / Detect changes (push) Waiting to run
CI / Platform (Go) (push) Blocked by required conditions
CI / Canvas (Next.js) (push) Blocked by required conditions
CI / Shellcheck (E2E scripts) (push) Blocked by required conditions
CI / Canvas Deploy Reminder (push) Blocked by required conditions
CI / Python Lint & Test (push) Waiting to run
E2E API Smoke Test / detect-changes (push) Waiting to run
E2E API Smoke Test / E2E API Smoke Test (push) Blocked by required conditions
E2E Chat / detect-changes (push) Waiting to run
E2E Chat / E2E Chat (push) Blocked by required conditions
E2E Staging Canvas (Playwright) / detect-changes (push) Waiting to run
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Blocked by required conditions
Handlers Postgres Integration / detect-changes (push) Waiting to run
Handlers Postgres Integration / Handlers Postgres Integration (push) Blocked by required conditions
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Waiting to run
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Waiting to run
publish-workspace-server-image / Production auto-deploy (push) Blocked by required conditions
Secret scan / Scan diff for credential-shaped strings (push) Waiting to run
Ops Scripts Tests / Ops scripts (unittest) (push) Waiting to run
CI / all-required (push) Has been cancelled
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 9s
publish-workspace-server-image / build-and-push (push) Has been cancelled
2026-05-26 00:20:18 +00:00
Molecule AI Dev Engineer A (Kimi) 6c6a070bc6 chore: re-trigger CI after stale status cleanup
Check migration collisions / Migration version collision check (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 4s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 9s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 7s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
Harness Replays / detect-changes (pull_request) Successful in 12s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 11s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 35s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Successful in 58s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m21s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 13s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 9s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m21s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 6s
CI / Platform (Go) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 4s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m28s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
qa-review / approved (pull_request) Failing after 3s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m21s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 7s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 59s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 13s
gate-check-v3 / gate-check (pull_request) Successful in 9s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 9s
E2E Chat / E2E Chat (pull_request) Successful in 8s
security-review / approved (pull_request) Failing after 4s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
Harness Replays / Harness Replays (pull_request) Successful in 5s
sop-checklist / all-items-acked (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 5s
CI / all-required (pull_request) Successful in 4m50s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 5s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m7s
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Successful in 5m25s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Successful in 8m0s
audit-force-merge / audit (pull_request) Successful in 10s
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 8s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
2026-05-25 22:20:24 +00:00
Molecule AI Dev Engineer B (MiniMax) d57404b87b fix(watchdog): close stale [main-red] issues on head-drift and CI-recovery
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 6s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 8s
CI / Detect changes (pull_request) Successful in 9s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 11s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
CI / all-required (pull_request) Successful in 23s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
gate-check-v3 / gate-check (pull_request) Successful in 20s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / all-items-acked (pull_request) Successful in 7s
CI / Platform (Go) (pull_request) Successful in 3s
sop-tier-check / tier-check (pull_request) Successful in 5s
CI / Canvas (Next.js) (pull_request) Successful in 4s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
E2E Chat / E2E Chat (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m12s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m15s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
qa-review / approved (pull_request) Refired via /qa-recheck; qa-review failed
security-review / approved (pull_request) Refired via /security-recheck; security-review failed
audit-force-merge / audit (pull_request) Successful in 10s
Issue molecule-core#1789: watchdog leaves stale open issues when main
force-pushes or CI recovers before the settling-window recheck completes.

Two bugs fixed:
1. HEAD-drift path: return path now calls close_open_red_issues_for_other_shas
   before exiting, so a force-push to SHA_NEW doesn't leave the SHA_OLD issue
   open. Prior code returned without closing anything.
2. CI-recovery path: same-SHA recovery now passes close_same_sha=True to
   close the issue for the current SHA too (recovery means we don't need
   it anymore). This required a new bool kwarg on close_open_red_issues_for
   _other_shas so green-path callers (initial combined=success) are still
   guarded against accidentally closing an issue they just filed.

Tests:
- test_head_drift_closes_stale_issue_for_prior_sha: stubs force-push
  SHA_NEW before recheck; verifies issue for SHA_RED is closed.
- test_recovery_on_same_sha_closes_issue_filed_on_prior_tick: stubs CI
  recovery on same SHA; verifies PATCH close is called with close_same_sha.

Stubs: _make_stub_api now supports sequential responses per (method, path)
via list values. Single-entry stubs unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 20:34:29 +00:00
hongming 648ac4d61b fix: clear main CI deploy blockers (#1856)
ci-arm64-advisory / fast-checks (push) Waiting to run
E2E Staging Canvas (Playwright) / detect-changes (push) Waiting to run
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Blocked by required conditions
Block internal-flavored paths / Block forbidden paths (push) Successful in 10s
CI / Python Lint & Test (push) Successful in 11s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 10s
Handlers Postgres Integration / detect-changes (push) Successful in 11s
Harness Replays / detect-changes (push) Successful in 11s
E2E API Smoke Test / detect-changes (push) Successful in 24s
E2E Chat / detect-changes (push) Successful in 22s
CI / Detect changes (push) Successful in 27s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 12s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 8s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Successful in 10s
Harness Replays / Harness Replays (push) Successful in 5s
publish-canvas-image / Build & push canvas image (push) Successful in 1m30s
Handlers Postgres Integration / Handlers Postgres Integration (push) Successful in 3m16s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 3m26s
CI / Shellcheck (E2E scripts) (push) Successful in 25s
E2E Chat / E2E Chat (push) Successful in 5m44s
publish-workspace-server-image / build-and-push (push) Successful in 7m56s
CI / Platform (Go) (push) Successful in 6m36s
CI / Canvas (Next.js) (push) Successful in 7m13s
CI / Canvas Deploy Reminder (push) Successful in 2s
CI / all-required (push) Successful in 9m24s
publish-workspace-server-image / Production auto-deploy (push) Successful in 3m43s
Sweep stale AWS Secrets Manager secrets / Sweep AWS Secrets Manager (push) Successful in 9s
Sweep stale Cloudflare Tunnels / Sweep CF tunnels (push) Successful in 1m30s
Staging SaaS smoke (every 30 min) / Staging SaaS smoke (push) Successful in 4m35s
main-red-watchdog / watchdog (push) Successful in 46s
Continuous synthetic E2E (staging) / Synthetic E2E against staging (push) Successful in 4m59s
gate-check-v3 / gate-check (push) Successful in 1m0s
Sweep stale e2e-* orgs (staging) / Sweep e2e orgs (push) Successful in 6s
Sweep stale Cloudflare DNS records / Sweep CF orphans (push) Successful in 22s
ci-required-drift / drift (push) Successful in 1m24s
Merge PR #1856 to clear stale CI expectations blocking display fix production deploy.
2026-05-25 20:09:54 +00:00
hongming 7bde0ea64a fix: clear main CI deploy blockers
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 8s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 3s
E2E API Smoke Test / detect-changes (pull_request) Successful in 7s
E2E Chat / detect-changes (pull_request) Successful in 7s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
Harness Replays / detect-changes (pull_request) Successful in 8s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
security-review / approved (pull_request) Successful in 9s
gate-check-v3 / gate-check (pull_request) Successful in 10s
qa-review / approved (pull_request) Successful in 10s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 5s
sop-tier-check / tier-check (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m12s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 30s
E2E Chat / E2E Chat (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 10s
Harness Replays / Harness Replays (pull_request) Successful in 10s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m50s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m33s
CI / Platform (Go) (pull_request) Successful in 5m20s
CI / Canvas (Next.js) (pull_request) Successful in 6m29s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 8m43s
audit-force-merge / audit (pull_request) Successful in 11s
2026-05-25 13:00:11 -07:00
hongming 89476ae330 Merge pull request 'fix: forward display keyboard and clipboard input' (#1854) from fix/display-keyboard-clipboard into main
ci-arm64-advisory / fast-checks (push) Waiting to run
CI / all-required (push) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 9s
Block internal-flavored paths / Block forbidden paths (push) Successful in 5s
CI / Python Lint & Test (push) Successful in 6s
CI / Detect changes (push) Successful in 9s
E2E API Smoke Test / detect-changes (push) Successful in 7s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 9s
Handlers Postgres Integration / detect-changes (push) Successful in 5s
E2E Chat / detect-changes (push) Successful in 9s
Harness Replays / detect-changes (push) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 6s
publish-canvas-image / Build & push canvas image (push) Successful in 1m48s
CI / Platform (Go) (push) Successful in 24s
CI / Shellcheck (E2E scripts) (push) Successful in 55s
E2E API Smoke Test / E2E API Smoke Test (push) Successful in 34s
Harness Replays / Harness Replays (push) Successful in 12s
publish-workspace-server-image / build-and-push (push) Successful in 3m19s
Handlers Postgres Integration / Handlers Postgres Integration (push) Failing after 3m23s
E2E Chat / E2E Chat (push) Successful in 6m2s
CI / Canvas (Next.js) (push) Failing after 7m26s
CI / Canvas Deploy Reminder (push) Has been skipped
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 9m11s
Sweep stale Cloudflare Tunnels / Sweep CF tunnels (push) Successful in 48s
Sweep stale e2e-* orgs (staging) / Sweep e2e orgs (push) Successful in 5s
Staging SaaS smoke (every 30 min) / Staging SaaS smoke (push) Successful in 5m44s
main-red-watchdog / watchdog (push) Successful in 2m18s
publish-workspace-server-image / Production auto-deploy (push) Failing after 30m11s
gate-check-v3 / gate-check (push) Successful in 32s
Continuous synthetic E2E (staging) / Synthetic E2E against staging (push) Successful in 5m19s
2026-05-25 19:34:15 +00:00
hongming cc55e651f6 fix: forward display keyboard and clipboard input
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
E2E API Smoke Test / E2E API Smoke Test (pull_request) Blocked by required conditions
E2E Chat / E2E Chat (pull_request) Blocked by required conditions
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Blocked by required conditions
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Blocked by required conditions
Harness Replays / Harness Replays (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 10s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 12s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 10s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 9s
Harness Replays / detect-changes (pull_request) Successful in 8s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 9s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 9s
gate-check-v3 / gate-check (pull_request) Successful in 10s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 11s
qa-review / approved (pull_request) Successful in 8s
security-review / approved (pull_request) Successful in 9s
sop-checklist / all-items-acked (pull_request) Successful in 8s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 5s
audit-force-merge / audit (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m4s
CI / all-required (pull_request) Failing after 40m29s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / Platform (Go) (pull_request) Has been cancelled
CI / Canvas (Next.js) (pull_request) Has been cancelled
CI / Shellcheck (E2E scripts) (pull_request) Has been cancelled
2026-05-25 12:30:43 -07:00
hongming e64b8f0f35 Merge pull request 'feat: refresh workspace templates from repo cache' (#1853) from fix/runtime-template-repo-cache into main
ci-arm64-advisory / fast-checks (push) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (push) Successful in 8s
publish-workspace-server-image / build-and-push (push) Successful in 3m9s
Block internal-flavored paths / Block forbidden paths (push) Successful in 7s
CI / Detect changes (push) Successful in 11s
CI / Python Lint & Test (push) Successful in 7s
E2E API Smoke Test / detect-changes (push) Successful in 14s
E2E Chat / detect-changes (push) Successful in 15s
E2E Staging Canvas (Playwright) / detect-changes (push) Successful in 12s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (push) Successful in 1m11s
E2E Staging SaaS (full lifecycle) / pr-validate (push) Successful in 1m4s
Handlers Postgres Integration / detect-changes (push) Successful in 20s
Harness Replays / detect-changes (push) Successful in 19s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (push) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (push) Successful in 10s
Secret scan / Scan diff for credential-shaped strings (push) Successful in 14s
gate-check-v3 / gate-check (push) Successful in 42s
Ops Scripts Tests / Ops scripts (unittest) (push) Successful in 1m33s
E2E Staging External Runtime / E2E Staging External Runtime (push) Successful in 5m35s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (push) Successful in 5m38s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (push) Successful in 7m50s
Sweep stale Cloudflare DNS records / Sweep CF orphans (push) Successful in 1m1s
CI / Canvas (Next.js) (push) Successful in 6s
CI / Shellcheck (E2E scripts) (push) Successful in 21s
ci-required-drift / drift (push) Successful in 1m33s
E2E API Smoke Test / E2E API Smoke Test (push) Failing after 3m23s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (push) Successful in 13s
Harness Replays / Harness Replays (push) Successful in 42s
CI / Canvas Deploy Reminder (push) Successful in 16s
Sweep stale e2e-* orgs (staging) / Sweep e2e orgs (push) Successful in 15s
Sweep stale AWS Secrets Manager secrets / Sweep AWS Secrets Manager (push) Successful in 13s
CI / Platform (Go) (push) Successful in 7m10s
Handlers Postgres Integration / Handlers Postgres Integration (push) Failing after 2m30s
CI / all-required (push) Successful in 1m49s
E2E Chat / E2E Chat (push) Successful in 6m19s
publish-workspace-server-image / Production auto-deploy (push) Has been cancelled
Staging SaaS smoke (every 30 min) / Staging SaaS smoke (push) Successful in 4m43s
Continuous synthetic E2E (staging) / Synthetic E2E against staging (push) Successful in 5m19s
2026-05-25 19:06:01 +00:00
Molecule AI Dev Engineer A (Kimi) 6f230fba38 style(scripts): fix E501 line too long in detect-changes.py and gitea-merge-queue.py
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Platform (Go) (pull_request) Blocked by required conditions
CI / Canvas (Next.js) (pull_request) Blocked by required conditions
CI / Shellcheck (E2E scripts) (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 9s
CI / all-required (pull_request) Failing after 40m27s
audit-force-merge / audit (pull_request) Has been skipped
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 18s
Check migration collisions / Migration version collision check (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 6s
CI / Detect changes (pull_request) Successful in 13s
E2E API Smoke Test / detect-changes (pull_request) Successful in 24s
E2E Chat / detect-changes (pull_request) Successful in 23s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 16s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 29s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 38s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 9s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Successful in 1m11s
Harness Replays / detect-changes (pull_request) Successful in 9s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 13s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m35s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 7s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m35s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m6s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m32s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m10s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 16s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
gate-check-v3 / gate-check (pull_request) Successful in 17s
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Successful in 5m23s
qa-review / approved (pull_request) Failing after 10s
security-review / approved (pull_request) Failing after 7s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 10s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 8s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Successful in 5m38s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m28s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m18s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 7s
E2E Chat / E2E Chat (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 12s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 8s
Harness Replays / Harness Replays (pull_request) Successful in 6s
CI / Canvas Deploy Reminder (pull_request) Has been cancelled
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:51:11 +00:00
Molecule AI Dev Engineer A (Kimi) 054ca2f552 style(scripts): fix remaining E501 line too long in ci-required-drift.py
4 locations missed in prior commit c326cad2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:51:11 +00:00
Molecule AI Dev Engineer A (Kimi) a120c86756 style(tools): fix ruff F401 and E741 in gate_check.py
Remove unused imports (time, Any, Optional) and rename ambiguous
variable l → role_login.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:51:11 +00:00
Molecule AI Dev Engineer A (Kimi) 5088a7273c style(scripts): fix ruff F401, F541, F841, E741 in 6 more files
- ci-required-drift.py: 4× F541 f-strings without placeholders
- lint-curl-status-capture.py: F401 unused sys import
- lint_bp_context_emit_match.py: E741 ambiguous variable l
- lint_continue_on_error_tracking.py: F401 unused timedelta import
- sop-checklist.py: F841 unused rejected_unknown, 2× E741 ambiguous l
- tests/_review_check_fixture.py: 3× F841 unused variables
- tests/test_lint_pre_flip_continue_on_error.py: F401 unused os import
- tests/test_sop_checklist.py: F401 unused tempfile import

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:51:11 +00:00
Molecule AI Dev Engineer A (Kimi) 57adcaae5f style(ci): fix E501 line too long in ci-required-drift.py
Break two over-long strings using implicit concatenation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:51:11 +00:00
Molecule AI Dev Engineer A (Kimi) eaf58bb8d4 style(tests): fix ruff F401, F541, F841, E741 in 10 files
Cleans up 22 ruff lint errors discovered by `ruff check --select=E,W,F`:

- F401 unused imports: json, sys, mock, textwrap (8 test files)
- F541 f-strings without placeholders: check_migration_collisions.py
- F841 unused variables: e, posted, old_title, per_context_iterated_for
- E741 ambiguous variable name `l` → `ln` in test_main_red_watchdog.py

All changes are test/script only; no production code affected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:51:11 +00:00
Molecule AI Dev Engineer A (Kimi) 93bd9c7295 style(scripts): auto-fix ruff F541, I001, F401 in 8 gitea scripts and tests
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 8s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 9s
CI / Python Lint & Test (pull_request) Successful in 10s
CI / Detect changes (pull_request) Successful in 14s
E2E API Smoke Test / detect-changes (pull_request) Successful in 8s
E2E Chat / detect-changes (pull_request) Successful in 8s
CI / all-required (pull_request) Successful in 4m4s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 7s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 3s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 12s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m3s
qa-review / approved (pull_request) Failing after 4s
security-review / approved (pull_request) Failing after 5s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m19s
CI / Platform (Go) (pull_request) Successful in 4s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 4s
E2E Chat / E2E Chat (pull_request) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
gate-check-v3 / gate-check (pull_request) Successful in 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 7s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 4s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m18s
audit-force-merge / audit (pull_request) Successful in 26s
Third batch of lint fixes (all auto-fixable):
- ci-required-drift.py: remove f-strings without placeholders (F541)
- detect-changes.py, lint-curl-status-capture.py, prod-auto-deploy.py,
  tests/*: sort imports (I001), remove unused imports (F401)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 19:06:16 +00:00
Molecule AI Dev Engineer A (Kimi) 3aee079310 test(handlers): move tokens_test.go behind integration build tag (RCA #1763 F3)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Python Lint & Test (pull_request) Successful in 10s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 12s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 20s
E2E API Smoke Test / detect-changes (pull_request) Successful in 18s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 16s
E2E Chat / detect-changes (pull_request) Successful in 17s
Harness Replays / detect-changes (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
qa-review / approved (pull_request) Failing after 8s
security-review / approved (pull_request) Failing after 5s
CI / Canvas (Next.js) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 6s
E2E Chat / E2E Chat (pull_request) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 18s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m8s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Harness Replays / Harness Replays (pull_request) Successful in 6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m46s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m24s
CI / Platform (Go) (pull_request) Successful in 5m6s
CI / all-required (pull_request) Successful in 6m5s
gate-check-v3 / gate-check (pull_request) Successful in 5s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 6s
sop-tier-check / tier-check (pull_request) Successful in 6s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m18s
audit-force-merge / audit (pull_request) Successful in 35s
tokens_test.go was the only DB-backed test in handlers/ that compiled in
regular test runs but silently skipped when db.DB == nil.  All other handler
tests use sqlmock; tokens_test.go needs a real Postgres because it exercises
workspace_auth_tokens row state end-to-end.

Move it behind //go:build integration, rename tests to TestIntegration_*, and
make setupTokenTestDB connect via INTEGRATION_DB_URL (with an explicit t.Skip
reason) so it runs in the existing Handlers Postgres Integration workflow.

This removes the silent skip from the regular Platform (Go) test job and makes
the test coverage visible in the explicitly-named optional workflow where a
real Postgres is provisioned.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:57:32 +00:00
Molecule AI Dev Engineer A (Kimi) cf932cf34c ci(deploy): align production auto-deploy wait timeout with CI drain time (RCA #1775)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 9s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 12s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
E2E Chat / detect-changes (pull_request) Successful in 11s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 13s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 9s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 7s
CI / all-required (pull_request) Successful in 26s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 5s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m15s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m10s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m17s
qa-review / approved (pull_request) Failing after 4s
security-review / approved (pull_request) Failing after 4s
CI / Platform (Go) (pull_request) Successful in 1s
CI / Canvas (Next.js) (pull_request) Successful in 2s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
E2E Chat / E2E Chat (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m30s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m13s
gate-check-v3 / gate-check (pull_request) Successful in 5s
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 14s
sop-tier-check / tier-check (pull_request) Successful in 13s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m19s
audit-force-merge / audit (pull_request) Successful in 26s
The deploy-production job timed out after 30m while push CI contexts
(e.g. Platform Go, Canvas, E2E, Postgres Integration) were still
draining. This produced false deploy-failure signal that contributed
to main-red noise.

Changes:
- Add CI_STATUS_TIMEOUT_SECONDS=3600 (60m) to the deploy-production
  env block, overriding the 1800s (30m) default in prod-auto-deploy.py.
- Raise job timeout-minutes from 75 → 90 so the longer wait plus
  redeploy-fleet + verification still fits comfortably within the
ceiling.

Fix classification: (a) single-line config change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:48:57 +00:00
38 changed files with 406 additions and 106 deletions
+18 -11
View File
@@ -274,7 +274,8 @@ def required_checks_env(audit_doc: dict) -> set[str]:
found.append(v)
if not found:
sys.stderr.write(
f"::error::REQUIRED_CHECKS env not found in any step of {AUDIT_WORKFLOW_PATH}\n"
f"::error::REQUIRED_CHECKS env not found in any step of "
f"{AUDIT_WORKFLOW_PATH}\n"
)
sys.exit(3)
if len(found) > 1:
@@ -387,7 +388,8 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
missing_from_needs = sorted(jobs - needs)
if missing_from_needs:
findings.append(
"F1 — jobs in ci.yml NOT under sentinel `needs:` (sentinel doesn't gate them):\n"
"F1 — jobs in ci.yml NOT under sentinel `needs:` "
"(sentinel doesn't gate them):\n"
+ "\n".join(f" - {n}" for n in missing_from_needs)
)
@@ -397,7 +399,8 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
stale_needs = sorted(needs - jobs_all)
if stale_needs:
findings.append(
"F1b — sentinel `needs:` lists jobs NOT present in ci.yml (typo or removed job):\n"
"F1b — sentinel `needs:` lists jobs NOT present in ci.yml "
"(typo or removed job):\n"
+ "\n".join(f" - {n}" for n in stale_needs)
)
@@ -405,7 +408,9 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
# Compute the contexts the CI YAML actually produces. The sentinel
# is in (B) intentionally (`ci / all-required (pull_request)`); we
# whitelist it explicitly.
emitted_contexts = {expected_context(j) for j in jobs} | {expected_context(SENTINEL_JOB)}
emitted_contexts = {
expected_context(j) for j in jobs
} | {expected_context(SENTINEL_JOB)}
# Contexts NOT produced by ci.yml may still come from other
# workflows in the repo (Secret scan etc). We can't enumerate
# every workflow's emissions cheaply; instead, flag only contexts
@@ -418,8 +423,9 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
)
if stale_protection:
findings.append(
"F2 — protection `status_check_contexts` entries with `ci / ` prefix that NO "
"job in ci.yml emits (stale name → silent advisory gate):\n"
"F2 — protection `status_check_contexts` entries with `ci / ` "
"prefix that NO job in ci.yml emits "
"(stale name → silent advisory gate):\n"
+ "\n".join(f" - {c}" for c in stale_protection)
)
@@ -494,7 +500,8 @@ def render_body(branch: str, findings: list[str], debug: dict) -> str:
f"# Drift detected on `{REPO}/{branch}`",
"",
"Auto-filed by `.gitea/workflows/ci-required-drift.yml` "
"(RFC [internal#219](https://git.moleculesai.app/molecule-ai/internal/issues/219) §4 + §6).",
"(RFC [internal#219]"
"(https://git.moleculesai.app/molecule-ai/internal/issues/219) §4 + §6).",
"",
"## Findings",
"",
@@ -547,12 +554,12 @@ def file_or_update(
if dry_run:
print(f"::notice::[dry-run] would file/update drift issue for {branch}")
print(f"::group::[dry-run] title")
print("::group::[dry-run] title")
print(title)
print(f"::endgroup::")
print(f"::group::[dry-run] body")
print("::endgroup::")
print("::group::[dry-run] body")
print(body)
print(f"::endgroup::")
print("::endgroup::")
return
existing = find_open_issue(title)
+4 -2
View File
@@ -15,7 +15,6 @@ import subprocess
import sys
from pathlib import Path
PROFILES: dict[str, dict[str, str]] = {
"ci": {
"platform": r"^workspace-server/",
@@ -153,7 +152,10 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
parser.add_argument("--event-name", default=os.environ.get("GITHUB_EVENT_NAME", ""))
parser.add_argument("--pr-base-sha", default="")
parser.add_argument("--base-ref", default="")
parser.add_argument("--push-before", default=os.environ.get("GITHUB_EVENT_BEFORE", ""))
parser.add_argument(
"--push-before",
default=os.environ.get("GITHUB_EVENT_BEFORE", ""),
)
return parser.parse_args(argv)
+3 -1
View File
@@ -183,7 +183,9 @@ def required_contexts_green(
status = latest_statuses.get(context)
state = status_state(status or {})
if state != "success":
if pr_labels and _is_tier_low_pending_ok(latest_statuses, context, pr_labels):
if pr_labels and _is_tier_low_pending_ok(
latest_statuses, context, pr_labels
):
continue # tier:low soft-fail: accept pending sop-checklist
missing_or_bad.append(f"{context}={state or 'missing'}")
return not missing_or_bad, missing_or_bad
@@ -13,11 +13,9 @@ from __future__ import annotations
import argparse
import glob
import re
import sys
from pathlib import Path
from typing import NamedTuple
SELF = ".gitea/workflows/lint-curl-status-capture.yml"
+1 -1
View File
@@ -283,7 +283,7 @@ def _ensure_labels(repo: str, names: list[str]) -> list[int]:
if status != "ok" or not isinstance(labels, list):
return []
out: list[int] = []
by_name = {l["name"]: l["id"] for l in labels if isinstance(l, dict)}
by_name = {label["name"]: label["id"] for label in labels if isinstance(label, dict)}
for n in names:
if n in by_name:
out.append(by_name[n])
@@ -82,7 +82,7 @@ import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
+21 -3
View File
@@ -578,6 +578,7 @@ def close_open_red_issues_for_other_shas(
current_sha: str,
*,
dry_run: bool = False,
close_same_sha: bool = False,
) -> int:
"""When main is green at current_sha, close any open `[main-red]`
issues whose title references a different SHA. Returns the number
@@ -586,15 +587,25 @@ def close_open_red_issues_for_other_shas(
Lineage note: we only close issues whose title prefix matches; if
a human renamed the issue or added a suffix this won't touch it.
That's intentional — manual editorial state takes precedence.
Args:
close_same_sha: set True when the caller already knows main is
green at current_sha (e.g. recovery block) and wants to close
the open issue for THIS SHA too. Defaults False so the
green-path callers never accidentally close an issue they just
filed on the same tick.
"""
target_title = title_for(current_sha)
open_red = list_open_red_issues()
closed = 0
for issue in open_red:
if issue.get("title") == target_title:
# Same SHA — caller should not have invoked this if main is
# green. Skip defensively.
continue
if not close_same_sha:
# Same SHA — caller should not have invoked this if main is
# green. Skip defensively (guards against green-path callers
# that accidentally pass the SHA they just filed for).
continue
# close_same_sha=True: close even this SHA's issue (recovery path)
num = issue.get("number")
if not isinstance(num, int):
continue
@@ -699,6 +710,10 @@ def run_once(*, dry_run: bool = False) -> int:
f"{sha[:10]} but HEAD is now {recheck_sha[:10]} on "
f"{WATCH_BRANCH}; next cron tick will re-evaluate."
)
# HEAD drifted — close any stale main-red issue for the prior SHA
# before returning, so we don't leave stale open issues when main
# is no longer pointing at the red commit.
close_open_red_issues_for_other_shas(recheck_sha, dry_run=dry_run)
return 0
recheck_status = get_combined_status(sha)
@@ -711,6 +726,9 @@ def run_once(*, dry_run: bool = False) -> int:
f"{recheck_status.get('state')!r} on recheck; "
f"initial red was a transient cancel-cascade."
)
# CI recovered on the same SHA — close any stale main-red issue
# that was filed on a prior tick for this SHA.
close_open_red_issues_for_other_shas(sha, dry_run=dry_run, close_same_sha=True)
return 0
# Still red after settling — file/update. Use the recheck data
-1
View File
@@ -17,7 +17,6 @@ import urllib.error
import urllib.request
from urllib.parse import quote
TRUE_VALUES = {"1", "true", "yes", "on", "disabled", "disable"}
PROD_CP_URL = "https://api.moleculesai.app"
DEFAULT_REQUIRED_CONTEXTS = [
+2
View File
@@ -12,6 +12,7 @@
# ≥ 1 review on the PR where:
# • state == APPROVED
# • review.dismissed == false
# • review.official != false (excludes draft/mis-filed APPROVED reviews)
# • review.user.login != PR.user.login (non-author)
# • review.user.login ∈ team-members
#
@@ -201,6 +202,7 @@ fi
JQ_FILTER='.[]
| select(.state == "APPROVED")
| select(.dismissed != true)
| select(.official != false)
| select(.user.login != $author)'
if [ "${REVIEW_CHECK_STRICT:-}" = "1" ]; then
JQ_FILTER="${JQ_FILTER}
+2 -3
View File
@@ -338,7 +338,6 @@ def compute_ack_state(
# Filter out self-acks and unknown slugs.
ackers_per_slug: dict[str, list[str]] = {s: [] for s in items_by_slug}
rejected_self: dict[str, list[str]] = {s: [] for s in items_by_slug}
rejected_unknown: dict[str, list[str]] = {s: [] for s in items_by_slug}
pending_team_check: dict[str, list[str]] = {s: [] for s in items_by_slug}
for (user, slug), kind in latest_directive.items():
@@ -842,7 +841,7 @@ def render_status(
def get_tier_mode(pr: dict[str, Any], cfg: dict[str, Any]) -> str:
"""Read tier label, return 'hard' or 'soft' per cfg.tier_failure_mode."""
labels = pr.get("labels") or []
tier_labels = [l.get("name", "") for l in labels if (l.get("name", "") or "").startswith("tier:")]
tier_labels = [label.get("name", "") for label in labels if (label.get("name", "") or "").startswith("tier:")]
mode_map = cfg.get("tier_failure_mode") or {}
default_mode = cfg.get("default_mode", "hard")
for tl in tier_labels:
@@ -865,7 +864,7 @@ def is_high_risk(pr: dict[str, Any], cfg: dict[str, Any]) -> bool:
Governance fix for internal#442 — closes the inconsistency between
sop-tier-check (tier-aware) and sop-checklist (was tier-blind).
"""
label_set = {(l.get("name") or "") for l in (pr.get("labels") or [])}
label_set = {(label.get("name") or "") for label in (pr.get("labels") or [])}
if "tier:high" in label_set:
return True
high_risk_labels = set(cfg.get("high_risk_labels") or [])
@@ -33,7 +33,6 @@ import re
import sys
import urllib.parse
STATE_DIR = os.environ.get("FIXTURE_STATE_DIR", "/tmp")
@@ -81,7 +80,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
# GET /repos/{owner}/{name}/pulls/{pr_number}
m = re.match(r"^/api/v1/repos/([^/]+)/([^/]+)/pulls/(\d+)$", path)
if m:
owner, name, pr_num = m.group(1), m.group(2), m.group(3)
pr_num = m.group(3)
if sc == "T2_pr_closed":
return self._json(200, {
"number": int(pr_num),
@@ -151,7 +150,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
# GET /teams/{team_id}/members/{username}
m = re.match(r"^/api/v1/teams/(\d+)/members/([^/]+)$", path)
if m:
team_id, login = m.group(1), m.group(2)
login = m.group(2)
if sc == "T8_team_not_member":
return self._empty(404)
if sc == "T9_team_403":
@@ -2,7 +2,6 @@ import importlib.util
import sys
from pathlib import Path
SCRIPT = Path(__file__).resolve().parents[1] / "gitea-merge-queue.py"
spec = importlib.util.spec_from_file_location("gitea_merge_queue", SCRIPT)
mq = importlib.util.module_from_spec(spec)
@@ -15,7 +15,6 @@ Mirrors the pattern in scripts/ops/test_check_migration_collisions.py
from __future__ import annotations
import importlib.util
import os
import sys
import unittest
from pathlib import Path
@@ -22,7 +22,6 @@ from __future__ import annotations
import os
import sys
import tempfile
import unittest
# Resolve sibling script regardless of where pytest is invoked from.
@@ -239,12 +239,13 @@ jobs:
# Publish/release lane (internal#462) — production deploy of a merged
# fix; reserved capacity, never queued behind PR-CI.
runs-on: publish
timeout-minutes: 75
timeout-minutes: 90
env:
CP_URL: ${{ vars.PROD_CP_URL || 'https://api.moleculesai.app' }}
CP_ADMIN_API_TOKEN: ${{ secrets.CP_ADMIN_API_TOKEN }}
GITEA_HOST: git.moleculesai.app
GITEA_TOKEN: ${{ secrets.PROD_AUTO_DEPLOY_CONTROL_TOKEN || secrets.AUTO_SYNC_TOKEN }}
CI_STATUS_TIMEOUT_SECONDS: "3600"
PROD_AUTO_DEPLOY_DISABLED: ${{ vars.PROD_AUTO_DEPLOY_DISABLED || secrets.PROD_AUTO_DEPLOY_DISABLED || '' }}
PROD_AUTO_DEPLOY_CANARY_SLUG: ${{ vars.PROD_AUTO_DEPLOY_CANARY_SLUG || 'hongming' }}
PROD_AUTO_DEPLOY_SOAK_SECONDS: ${{ vars.PROD_AUTO_DEPLOY_SOAK_SECONDS || '60' }}
+12
View File
@@ -46,6 +46,18 @@
---
## Quick Start
```bash
git clone https://git.moleculesai.app/molecule-ai/molecule-monorepo.git
cd molecule-monorepo
./scripts/dev-start.sh
```
Then open [http://localhost:3000](http://localhost:3000), add your model API key in **Config → Secrets & API Keys → Global**, and create a workspace from a template.
See the full [Quickstart Guide](./docs/quickstart.md) for prerequisites, manual setup, and troubleshooting.
## The Pitch
Molecule AI is the most powerful way to govern an AI agent organization in production.
+4 -2
View File
@@ -224,12 +224,14 @@ export function Toolbar() {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key !== "?") return;
const tag = (e.target as HTMLElement).tagName;
const target = e.target as HTMLElement;
if (target.closest?.('[data-display-stream="true"]')) return;
const tag = target.tagName;
const inInput =
tag === "INPUT" ||
tag === "TEXTAREA" ||
tag === "SELECT" ||
(e.target as HTMLElement).isContentEditable;
target.isContentEditable;
if (inInput) return;
// Don't fire when a modal/dialog is already mounted (canvas modals,
// side panel, etc. use z-50 or above).
@@ -201,15 +201,13 @@ describe("CreateWorkspaceDialog — WCAG SC 1.3.1 label/input association", () =
expect(label?.textContent).toContain("Budget limit");
});
it("Template input has a <label> whose htmlFor matches the input id", async () => {
it("Workspace Template select has a <label> whose htmlFor matches the select id", async () => {
await openDialog();
const templateInput = screen.getByPlaceholderText(
"e.g. seo-agent (from workspace-configs-templates/)"
) as HTMLInputElement;
expect(templateInput.id).toBeTruthy();
const label = document.querySelector(`label[for="${templateInput.id}"]`);
const templateSelect = screen.getByLabelText("Workspace Template") as HTMLSelectElement;
expect(templateSelect.id).toBeTruthy();
const label = document.querySelector(`label[for="${templateSelect.id}"]`);
expect(label).toBeTruthy();
expect(label?.textContent).toContain("Template");
expect(label?.textContent).toContain("Workspace Template");
});
it("each InputField generates a distinct id (no id collisions)", async () => {
@@ -218,13 +216,16 @@ describe("CreateWorkspaceDialog — WCAG SC 1.3.1 label/input association", () =
screen.getByPlaceholderText("e.g. SEO Agent"),
screen.getByPlaceholderText("e.g. SEO Specialist"),
screen.getByPlaceholderText("e.g. 100"),
screen.getByPlaceholderText("e.g. seo-agent (from workspace-configs-templates/)"),
] as HTMLInputElement[];
const selects = [
screen.getByLabelText("Runtime"),
screen.getByLabelText("Workspace Template"),
] as HTMLSelectElement[];
const ids = inputs.map((i) => i.id).filter(Boolean);
const ids = [...inputs, ...selects].map((i) => i.id).filter(Boolean);
const unique = new Set(ids);
expect(unique.size).toBe(ids.length); // no duplicates
expect(ids.length).toBe(4);
expect(ids.length).toBe(5);
});
it("Name label text contains the required asterisk indicator", async () => {
@@ -68,7 +68,11 @@ afterEach(() => {
function ShortcutTestComponent() {
useKeyboardShortcuts();
return <div data-testid="canvas-root" />;
return (
<div data-testid="canvas-root">
<div data-testid="display-stream" data-display-stream="true" />
</div>
);
}
function renderWithProvider() {
@@ -78,6 +82,13 @@ function renderWithProvider() {
// ─── Tests ───────────────────────────────────────────────────────────────────
describe("Esc — deselect / close context menu", () => {
it("does not handle keys targeted at the display stream", () => {
mockStoreState.contextMenu = { x: 100, y: 100, nodeId: "n1" };
const { getByTestId } = renderWithProvider();
fireEvent.keyDown(getByTestId("display-stream"), { key: "Escape" });
expect(mockStoreState.closeContextMenu).not.toHaveBeenCalled();
});
it("closes the context menu when one is open", () => {
mockStoreState.contextMenu = { x: 100, y: 100, nodeId: "n1" };
renderWithProvider();
@@ -28,12 +28,14 @@ function hasChildren(nodeId: string, nodes: Node<WorkspaceNodeData>[]): boolean
export function useKeyboardShortcuts() {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
const tag = (e.target as HTMLElement).tagName;
const target = e.target as HTMLElement;
if (target.closest?.('[data-display-stream="true"]')) return;
const tag = target.tagName;
const inInput =
tag === "INPUT" ||
tag === "TEXTAREA" ||
tag === "SELECT" ||
(e.target as HTMLElement).isContentEditable;
target.isContentEditable;
if (e.key === "Escape") {
const state = useCanvasStore.getState();
+98 -1
View File
@@ -313,11 +313,21 @@ function DisplayControlBar({
function DesktopStream({ sessionUrl }: { sessionUrl: string }) {
const containerRef = useRef<HTMLDivElement | null>(null);
const rfbRef = useRef<RFB | null>(null);
const [streamError, setStreamError] = useState<string | null>(null);
const [clipboardStatus, setClipboardStatus] = useState<string | null>(null);
const [remoteClipboardText, setRemoteClipboardText] = useState("");
useEffect(() => {
let cancelled = false;
let rfb: RFB | null = null;
let clipboardTimer: ReturnType<typeof setTimeout> | null = null;
const setTemporaryClipboardStatus = (message: string) => {
setClipboardStatus(message);
if (clipboardTimer) clearTimeout(clipboardTimer);
clipboardTimer = setTimeout(() => setClipboardStatus(null), 2500);
};
async function connect() {
setStreamError(null);
@@ -328,9 +338,19 @@ function DesktopStream({ sessionUrl }: { sessionUrl: string }) {
rfb = new mod.default(containerRef.current, stream.url, {
wsProtocols: ["binary", `molecule-display-token.${stream.token}`],
});
rfbRef.current = rfb;
rfb.scaleViewport = true;
rfb.resizeSession = true;
rfb.focusOnClick = true;
rfb.focus({ preventScroll: true });
rfb.addEventListener("clipboard", (event: Event) => {
const text = (event as CustomEvent<{ text?: string }>).detail?.text ?? "";
if (!text) return;
setRemoteClipboardText(text);
void navigator.clipboard?.writeText(text)
.then(() => setTemporaryClipboardStatus("Copied remote clipboard"))
.catch(() => setTemporaryClipboardStatus("Remote clipboard ready"));
});
rfb.addEventListener("disconnect", (event: Event) => {
const detail = (event as CustomEvent<{ clean?: boolean }>).detail;
if (!cancelled && !detail?.clean) setStreamError("Desktop stream disconnected.");
@@ -343,13 +363,83 @@ function DesktopStream({ sessionUrl }: { sessionUrl: string }) {
connect();
return () => {
cancelled = true;
if (clipboardTimer) clearTimeout(clipboardTimer);
rfbRef.current = null;
rfb?.disconnect();
};
}, [sessionUrl]);
useEffect(() => {
const onPaste = (event: ClipboardEvent) => {
if (!isDisplayEventTarget(containerRef.current, event.target)) return;
const text = event.clipboardData?.getData("text/plain") ?? "";
if (!text) return;
event.preventDefault();
rfbRef.current?.clipboardPasteFrom(text);
rfbRef.current?.focus({ preventScroll: true });
setClipboardStatus("Pasted to desktop");
};
window.addEventListener("paste", onPaste, true);
return () => window.removeEventListener("paste", onPaste, true);
}, []);
const pasteLocalClipboard = async () => {
try {
const text = await navigator.clipboard?.readText();
if (!text) {
setClipboardStatus("Clipboard is empty");
return;
}
rfbRef.current?.clipboardPasteFrom(text);
rfbRef.current?.focus({ preventScroll: true });
setClipboardStatus("Pasted to desktop");
} catch {
setClipboardStatus("Press Ctrl/Cmd+V while the desktop is focused");
}
};
const copyRemoteClipboard = async () => {
if (!remoteClipboardText) {
setClipboardStatus("No remote clipboard yet");
return;
}
try {
await navigator.clipboard.writeText(remoteClipboardText);
setClipboardStatus("Copied remote clipboard");
} catch {
setClipboardStatus("Browser blocked clipboard copy");
}
};
return (
<div className="relative min-h-0 flex-1 bg-black">
<div
data-display-stream="true"
className="relative min-h-0 flex-1 bg-black"
onMouseDown={() => rfbRef.current?.focus({ preventScroll: true })}
>
<div ref={containerRef} title="Workspace desktop" className="h-full w-full overflow-hidden bg-black" />
<div className="absolute right-3 top-3 flex items-center gap-2">
{clipboardStatus && (
<span className="rounded border border-line/50 bg-black/80 px-2 py-1 text-[10px] text-white">
{clipboardStatus}
</span>
)}
<button
type="button"
onClick={pasteLocalClipboard}
className="h-7 rounded border border-line/50 bg-black/75 px-2 text-[10px] font-medium text-white hover:bg-black"
>
Paste
</button>
<button
type="button"
onClick={copyRemoteClipboard}
className="h-7 rounded border border-line/50 bg-black/75 px-2 text-[10px] font-medium text-white hover:bg-black disabled:cursor-not-allowed disabled:opacity-50"
disabled={!remoteClipboardText}
>
Copy
</button>
</div>
{streamError && (
<div className="absolute inset-x-4 top-4 rounded border border-red-500/30 bg-red-950/80 px-3 py-2 text-[11px] text-red-100">
{streamError}
@@ -359,6 +449,13 @@ function DesktopStream({ sessionUrl }: { sessionUrl: string }) {
);
}
function isDisplayEventTarget(container: HTMLElement | null, target: EventTarget | null): boolean {
if (!container) return false;
if (target instanceof Node && container.contains(target)) return true;
const active = document.activeElement;
return active instanceof Node && container.contains(active);
}
function displayWebSocketConnection(sessionUrl: string): { url: string; token: string } {
const url = new URL(sessionUrl, window.location.href);
const token = new URLSearchParams(url.hash.replace(/^#/, "")).get("token") ?? "";
@@ -2,10 +2,12 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
const { mockGet, mockPost, mockRFBConstructor } = vi.hoisted(() => ({
const { mockGet, mockPost, mockRFBConstructor, mockRFBClipboardPasteFrom, mockRFBFocus } = vi.hoisted(() => ({
mockGet: vi.fn(),
mockPost: vi.fn(),
mockRFBConstructor: vi.fn(),
mockRFBClipboardPasteFrom: vi.fn(),
mockRFBFocus: vi.fn(),
}));
vi.mock("@/lib/api", () => ({
@@ -30,6 +32,12 @@ vi.mock("@novnc/novnc", () => ({
this.options = options;
mockRFBConstructor(target, url, options);
}
clipboardPasteFrom(text: string) {
mockRFBClipboardPasteFrom(text);
}
focus(options?: FocusOptions) {
mockRFBFocus(options);
}
disconnect() {}
},
}));
@@ -42,6 +50,8 @@ describe("DisplayTab", () => {
mockGet.mockReset();
mockPost.mockReset();
mockRFBConstructor.mockReset();
mockRFBClipboardPasteFrom.mockReset();
mockRFBFocus.mockReset();
});
it("renders unavailable state for non-display workspaces", async () => {
@@ -157,6 +167,43 @@ describe("DisplayTab", () => {
expect(mockRFBConstructor.mock.calls[0][1]).not.toContain("token=");
});
it("forwards browser paste events into the noVNC clipboard", async () => {
mockGet
.mockResolvedValueOnce({
available: true,
mode: "desktop-control",
protocol: "novnc",
width: 1920,
height: 1080,
})
.mockResolvedValueOnce({
controller: "none",
});
mockPost.mockResolvedValueOnce({
controller: "user",
controlled_by: "admin-token",
expires_at: "2026-05-23T08:48:27Z",
session_url: "/workspaces/ws-display/display/session/websockify#token=signed",
});
render(<DisplayTab workspaceId="ws-display" />);
await waitFor(() => {
expect(screen.getByRole("button", { name: "Take control" })).toBeTruthy();
});
fireEvent.click(screen.getByRole("button", { name: "Take control" }));
const desktop = await screen.findByTitle("Workspace desktop");
fireEvent.paste(desktop, {
clipboardData: {
getData: (type: string) => (type === "text/plain" ? "Paste Me" : ""),
},
});
expect(mockRFBClipboardPasteFrom).toHaveBeenCalledWith("Paste Me");
expect(mockRFBFocus).toHaveBeenCalledWith({ preventScroll: true });
});
it("releases user display control", async () => {
mockGet
.mockResolvedValueOnce({
@@ -15,6 +15,8 @@ export function useKeyboardShortcut(
if (!enabled) return;
function handler(e: KeyboardEvent) {
const target = e.target as HTMLElement;
if (target.closest?.('[data-display-stream="true"]')) return;
if (e.key !== key) return;
if (meta && !e.metaKey) return;
if (ctrl && !e.ctrlKey) return;
+2
View File
@@ -4,6 +4,8 @@ declare module "@novnc/novnc" {
resizeSession: boolean;
focusOnClick: boolean;
constructor(target: HTMLElement, url: string, options?: { wsProtocols?: string[]; [key: string]: unknown });
clipboardPasteFrom(text: string): void;
disconnect(): void;
focus(options?: FocusOptions): void;
}
}
@@ -70,7 +70,7 @@ def test_diag_memory_root_writable_in_canary_mode(sim: CPSim) -> None:
key = f"canary-probe-{uuid.uuid4().hex[:8]}"
try:
val = sim.probe_memory(key)
except Exception as e:
except Exception:
# /mcp may not be exposed on this template — canary 4 will
# surface the real defect if memory is actually broken.
if os.environ.get("CANARY_STRICT_MCP") == "1":
+2 -2
View File
@@ -281,8 +281,8 @@ def main() -> int:
for prefix, peers in sorted(open_pr_collisions.items()):
peer_str = ", ".join(f"#{p['number']} ({p['headRefName']})" for p in peers)
print(f"::error::migration prefix {prefix:03d} also claimed by open PR(s): {peer_str}")
print(f"::error::rebase coordination needed — only one PR can land a given prefix; "
f"renumber yours or theirs")
print("::error::rebase coordination needed — only one PR can land a given prefix; "
"renumber yours or theirs")
return 1
+9 -6
View File
@@ -4,9 +4,10 @@
# Round-trip: register a workspace as poll-mode (no callback URL) → POST a
# multi-file chat upload → verify each file becomes (a) one
# `chat_upload_receive` activity row and (b) one /pending-uploads row → fetch
# the bytes back via the poll endpoint → ack → verify the row 404s on
# subsequent fetch. Also pins cross-workspace bleed protection: workspace B
# cannot read workspace A's pending uploads even with its own valid bearer.
# the bytes back via the poll endpoint → ack → verify the row stays readable
# during retention for refreshed canvas previews. Also pins cross-workspace
# bleed protection: workspace B cannot read workspace A's pending uploads even
# with its own valid bearer.
#
# Why this exists separately from test_chat_upload_e2e.sh: that script
# covers the PUSH path (the workspace's own /internal/chat/uploads/ingest).
@@ -218,14 +219,16 @@ case "$RE_ACK1_CODE" in
;;
esac
# ---------- Phase 7: GET content after ack returns 404 ----------
# ---------- Phase 7: GET content after ack remains readable ----------
echo ""
echo "--- Phase 7: Acked file 404s on subsequent fetch ---"
echo "--- Phase 7: Acked file remains readable during retention ---"
POST_ACK=$(curl -s -w '\n%{http_code}' --max-time "$TIMEOUT" -H "Authorization: Bearer $TOK_A" \
"$BASE/workspaces/$WS_A/pending-uploads/$FID1/content")
POST_ACK_CODE=$(printf '%s' "$POST_ACK" | tail -n1)
check_eq "acked alpha returns HTTP 404" "404" "$POST_ACK_CODE"
POST_ACK_BODY=$(printf '%s' "$POST_ACK" | sed '$d')
check_eq "acked alpha returns HTTP 200" "200" "$POST_ACK_CODE"
check_eq "acked alpha bytes still readable" "$EXPECTED1" "$POST_ACK_BODY"
# ---------- Phase 8: cross-workspace bleed protection ----------
echo ""
-2
View File
@@ -18,9 +18,7 @@ No network. No live Gitea calls.
from __future__ import annotations
import importlib.util
import json
import os
import sys
import textwrap
from pathlib import Path
from unittest import mock
+1 -3
View File
@@ -55,9 +55,7 @@ from __future__ import annotations
import importlib.util
import os
import sys
from pathlib import Path
from unittest import mock
import pytest
@@ -164,7 +162,7 @@ def test_bp_orphan_context_fails(envset, monkeypatch, capsys):
" all-required:\n runs-on: x\n steps:\n - run: echo hi\n",
)
m = _import_lint()
posted = _stub_api(
_stub_api(
monkeypatch,
m,
("ok", {"status_check_contexts": [
@@ -60,10 +60,8 @@ from __future__ import annotations
import importlib.util
import os
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest import mock
import pytest
-3
View File
@@ -53,10 +53,7 @@ from __future__ import annotations
import importlib.util
import os
import subprocess
import sys
import textwrap
from pathlib import Path
from unittest import mock
import pytest
@@ -61,9 +61,7 @@ from __future__ import annotations
import importlib.util
import os
import subprocess
import sys
from pathlib import Path
from unittest import mock
import pytest
-2
View File
@@ -38,9 +38,7 @@ from __future__ import annotations
import importlib.util
import os
import sys
from pathlib import Path
from unittest import mock
import pytest
+108 -11
View File
@@ -37,7 +37,6 @@ from __future__ import annotations
import importlib.util
import json
import os
import sys
import urllib.error
from pathlib import Path
from unittest import mock
@@ -117,15 +116,25 @@ def _make_stub_api(responses: dict):
def __call__(self, method, path, *, body=None, query=None, expect_json=True):
self.calls.append((method, path, body, query))
# If we've stored a list for this (method, path), rotate through.
# This supports tests that need sequential responses for the
# same endpoint without adding query-param noise.
key = (method, path)
if key not in responses:
raise AssertionError(
f"unexpected api call: {method} {path} (no stub registered)"
)
r = responses[key]
if isinstance(r, Exception):
raise r
return r
r = responses.get(key)
if isinstance(r, list):
if not r:
raise AssertionError(
f"stub sequential responses exhausted for {method} "
f"{path} — provisioned {len(r)} entries"
)
return r.pop(0)
if r is not None:
if isinstance(r, Exception):
raise r
return r
raise AssertionError(
f"unexpected api call: {method} {path} (no stub registered)"
)
return StubApi()
@@ -133,6 +142,7 @@ def _make_stub_api(responses: dict):
# Sample SHA used throughout. 40 chars per Gitea convention.
SHA_RED = "deadbeefcafe1234567890abcdef000011112222"
SHA_GREEN = "ababababcdcdcdcd0000111122223333deadc0de"
SHA_NEW = "aaaabbbbccccddddeeeeffff0000111122223333"
def _branches_response(sha: str) -> dict:
@@ -140,6 +150,19 @@ def _branches_response(sha: str) -> dict:
return {"name": "main", "commit": {"id": sha}}
def _branch_alt(sha: str) -> dict:
"""Identical shape but to a different key path so _make_stub_api
retains a separate first-response entry from the primary
_branches_response() path.
The stub stores only the first response per (method, path) pair.
Tests that need two distinct responses for the same logical
GET /branches/main call use _branch_alt for the second lookup so
the stub returns the correct sequential entry.
"""
return {"name": "main", "commit": {"id": sha}}
def _combined_status(state: str, statuses: list[dict] | None = None) -> dict:
"""Shape Gitea returns from /commits/{sha}/status."""
return {"state": state, "statuses": statuses or []}
@@ -542,7 +565,6 @@ def test_auto_close_skips_when_main_pending(wd_module, monkeypatch):
"""main pending (CI still running) at NEW_SHA → leave old issue alone.
Pending could resolve to red, so closing prematurely would lose the
breadcrumb of the prior red."""
old_title = f"[main-red] owner/repo: {SHA_RED[:10]}"
stub = _make_stub_api({
("GET", "/repos/owner/repo/branches/main"): (200, _branches_response(SHA_GREEN)),
("GET", f"/repos/owner/repo/commits/{SHA_GREEN}/status"): (
@@ -561,6 +583,81 @@ def test_auto_close_skips_when_main_pending(wd_module, monkeypatch):
assert ("GET", "/repos/owner/repo/issues") not in methods_paths
# --------------------------------------------------------------------------
# Stale-issue cleanup on transient / head-drift (internal#1789)
# --------------------------------------------------------------------------
def test_head_drift_closes_stale_issue_for_prior_sha(wd_module, monkeypatch):
"""Initial red at SHA_RED. Before recheck, main is force-pushed to
SHA_NEW (different commit). watchdog must close the stale SHA_RED
issue before returning — otherwise stale open issues accumulate
when main is force-pushed during a red window."""
stub = _make_stub_api({
# Initial check: branch SHA_RED, status failure
("GET", "/repos/owner/repo/branches/main"): [
(200, _branches_response(SHA_RED)),
(200, _branch_alt(SHA_NEW)), # recheck branch call → HEAD moved
(200, _branch_alt(SHA_NEW)), # close path branch call
],
("GET", f"/repos/owner/repo/commits/{SHA_RED}/status"): [
(200, _combined_status("failure", [
{"context": "ci/test", "status": "failure", "description": "broke"},
])),
(200, _combined_status("success", [ # recheck: CI result arrived
{"context": "ci/test", "status": "success"},
])),
],
(f"GET", f"/repos/owner/repo/commits/{SHA_NEW}/status"): [
(200, _combined_status("success", [
{"context": "ci/test", "status": "success"},
])),
],
# close_open_red_issues_for_other_shas(SHA_NEW): issue for SHA_RED found
("GET", "/repos/owner/repo/issues"): [
(200, [{"number": 9, "title": f"[main-red] owner/repo: {SHA_RED[:10]}"}]),
],
("POST", "/repos/owner/repo/issues/9/comments"): (201, {"id": 200}),
("PATCH", "/repos/owner/repo/issues/9"): (200, {"number": 9, "state": "closed"}),
})
monkeypatch.setattr(wd_module, "api", stub)
rc = wd_module.run_once(dry_run=False)
assert rc == 0
methods_paths = [(c[0], c[1]) for c in stub.calls]
assert ("PATCH", "/repos/owner/repo/issues/9") in methods_paths, \
"head-drift should close the stale SHA_RED issue"
def test_recovery_on_same_sha_closes_issue_filed_on_prior_tick(wd_module, monkeypatch):
"""Same SHA shows red on initial check, but CI recovers before recheck
completes. watchdog must close the issue that was filed on an earlier
tick for this same SHA — otherwise stale open issues accumulate when CI
recovers within the settling window."""
stub = _make_stub_api({
("GET", "/repos/owner/repo/branches/main"): (200, _branches_response(SHA_RED)),
# Sequential: initial check → failure, recheck (≥2nd call) → success.
# Using a list so Python dict keeps a single key (avoids overwrite).
("GET", f"/repos/owner/repo/commits/{SHA_RED}/status"): [
(200, _combined_status("failure", [
{"context": "ci/test", "status": "failure", "description": "broke"},
])),
(200, _combined_status("success", [
{"context": "ci/test", "state": "success"},
])),
],
# List open red issues → find stale issue for this SHA
("GET", "/repos/owner/repo/issues"): (
200, [{"number": 11, "title": f"[main-red] owner/repo: {SHA_RED[:10]}"}],
),
("POST", "/repos/owner/repo/issues/11/comments"): (201, {"id": 300}),
("PATCH", "/repos/owner/repo/issues/11"): (200, {"number": 11, "state": "closed"}),
})
monkeypatch.setattr(wd_module, "api", stub)
rc = wd_module.run_once(dry_run=False)
assert rc == 0
methods_paths = [(c[0], c[1]) for c in stub.calls]
assert ("PATCH", "/repos/owner/repo/issues/11") in methods_paths, \
"recovery-on-same-SHA should close the stale issue"
# --------------------------------------------------------------------------
# HTTP-failure / api() raises — duplicate-write regression guard
# --------------------------------------------------------------------------
@@ -790,7 +887,7 @@ def test_emit_loki_event_prints_json_line(wd_module, capsys, monkeypatch):
captured = capsys.readouterr()
assert "main-red-watchdog event:" in captured.out
# Find the JSON payload after the prefix and verify it parses
line = [l for l in captured.out.splitlines() if "main-red-watchdog event:" in l][0]
line = [ln for ln in captured.out.splitlines() if "main-red-watchdog event:" in ln][0]
payload = json.loads(line.split("main-red-watchdog event:", 1)[1].strip())
assert payload["event_type"] == "main_red_detected"
assert payload["repo"] == "owner/repo"
-2
View File
@@ -40,7 +40,6 @@ Dependencies: stdlib + pytest + PyYAML. No network.
from __future__ import annotations
import importlib.util
import json
import os
import sys
from pathlib import Path
@@ -853,7 +852,6 @@ def test_reap_skips_combined_success_shas(sr_module, monkeypatch):
Mock 2 SHAs with combined=success + 1 with combined=failure → only
the failure-SHA's statuses get the per-context loop applied.
"""
per_context_iterated_for: list[str] = []
posts: list[tuple[str, dict]] = []
failure_statuses = [
+3 -5
View File
@@ -23,11 +23,9 @@ import json
import os
import re
import sys
import time
import urllib.request
import urllib.error
from datetime import datetime, timezone
from typing import Any, Optional
# ── Gitea API client ────────────────────────────────────────────────────────
@@ -160,9 +158,9 @@ def signal_1_comment_scan(pr_number: int, repo: str) -> dict:
# Build reverse map: login -> (group, agent_key)
login_to_group = {}
for group, login in relevant_roles.items():
for role, l in AGENT_LOGIN_MAP.items():
if l == login:
login_to_group[l] = (group, f"core-{role}")
for role, role_login in AGENT_LOGIN_MAP.items():
if role_login == login:
login_to_group[role_login] = (group, f"core-{role}")
# Collect all agent-tag matches from comments
comments = []
@@ -124,13 +124,17 @@ func TestIntegration_PendingUploads_PutGetAckRoundTrip(t *testing.T) {
t.Errorf("FetchedAt should be set after MarkFetched")
}
// Ack flips acked_at; subsequent Gets return ErrNotFound (acked rows
// are filtered out at the SELECT predicate).
// Ack flips acked_at. Acked rows remain readable during retention so
// refreshed canvas previews can resolve platform-pending: attachment URIs.
if err := store.Ack(ctx, fileID); err != nil {
t.Fatalf("Ack: %v", err)
}
if _, err := store.Get(ctx, fileID); err != pendinguploads.ErrNotFound {
t.Errorf("Get after Ack: got %v, want ErrNotFound", err)
rec3, err := store.Get(ctx, fileID)
if err != nil {
t.Fatalf("Get after Ack: %v", err)
}
if rec3.AckedAt == nil {
t.Errorf("AckedAt should be set after Ack")
}
// Idempotent re-ack succeeds.
@@ -1,3 +1,6 @@
//go:build integration
// +build integration
package handlers
import (
@@ -6,6 +9,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"git.moleculesai.app/molecule-ai/molecule-core/workspace-server/internal/db"
@@ -16,22 +20,31 @@ import (
func init() { gin.SetMode(gin.TestMode) }
// setupTokenTestDB creates an in-memory SQLite-like test or returns early
// if the real Postgres test DB is available. For unit tests we use the
// package-level db.DB which handlers rely on.
// setupTokenTestDB connects to $INTEGRATION_DB_URL (skipping the test if
// unset), sets the package-global db.DB for the duration of the test, and
// returns a cleanup func that restores the previous db.DB value.
func setupTokenTestDB(t *testing.T) func() {
t.Helper()
if db.DB == nil {
t.Skip("db.DB not initialised — run with a test database")
url := os.Getenv("INTEGRATION_DB_URL")
if url == "" {
t.Skip("INTEGRATION_DB_URL not set; skipping (local devs: start a Postgres container and export INTEGRATION_DB_URL)")
}
// Quick probe — if the DB is closed or unreachable, skip.
if err := db.DB.Ping(); err != nil {
t.Skipf("db.DB not reachable: %v", err)
conn, err := sql.Open("postgres", url)
if err != nil {
t.Fatalf("open integration DB: %v", err)
}
if err := conn.Ping(); err != nil {
t.Fatalf("ping integration DB: %v", err)
}
prevDB := db.DB
db.DB = conn
return func() {
db.DB = prevDB
conn.Close()
}
return func() {}
}
func TestTokenHandler_CreateAndList(t *testing.T) {
func TestIntegration_TokenHandler_CreateAndList(t *testing.T) {
cleanup := setupTokenTestDB(t)
defer cleanup()
@@ -94,7 +107,7 @@ func TestTokenHandler_CreateAndList(t *testing.T) {
}
}
func TestTokenHandler_Revoke(t *testing.T) {
func TestIntegration_TokenHandler_Revoke(t *testing.T) {
cleanup := setupTokenTestDB(t)
defer cleanup()
@@ -151,7 +164,7 @@ func TestTokenHandler_Revoke(t *testing.T) {
}
}
func TestTokenHandler_RevokeWrongWorkspace(t *testing.T) {
func TestIntegration_TokenHandler_RevokeWrongWorkspace(t *testing.T) {
cleanup := setupTokenTestDB(t)
defer cleanup()