Skip to content

fix(ci): hard-fail sweep-cf-orphans on schedule when secrets missing - #2248

Merged
hongmingwang-moleculeai merged 1 commit into
stagingfrom
fix/sweep-cf-orphans-hard-fail-on-schedule
Apr 29, 2026
Merged

fix(ci): hard-fail sweep-cf-orphans on schedule when secrets missing#2248
hongmingwang-moleculeai merged 1 commit into
stagingfrom
fix/sweep-cf-orphans-hard-fail-on-schedule

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

  • Convert the hourly CF orphan sweeper's secrets-missing handling: schedule/workflow_run/push trigger → hard-fail (exit 1). workflow_dispatch keeps the soft-skip (operator override).
  • Removes the silent-success path that masked a real leak from [unknown date] to 2026-04-28.

What incident this guards against

Until 2026-04-28, all six secrets (CF_API_TOKEN, CF_ZONE_ID, CP_PROD_ADMIN_TOKEN, CP_STAGING_ADMIN_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) were unset on this repo. Every hourly tick printed a yellow ::warning:: and exited 0 — GitHub Actions reports those runs as completed/success. The sweeper was indistinguishable from a healthy janitor with nothing to do, while CF orphans accumulated to 152/200 zone records (~76% of the quota gone) under a steady-state leak fixed in a separate PR (controlplane#318).

A red CI run prompts investigation. A green CI run is presumed healthy. Anything in between is the failure mode this PR closes.

Schedule-vs-dispatch split

Trigger Behaviour Rationale
schedule exit 1 with three ::error:: lines Surfaces the gap on the next tick
workflow_run (future) exit 1 Same — automated invocation, no operator
push (future) exit 1 Same
workflow_dispatch exit 0 with ::warning:: Operator already accepted repo state; lets them short-circuit a deliberate rerun

Test plan

  • python3 -c 'import yaml; yaml.safe_load(open(...))' parses clean
  • Embedded shell bash -n parses clean
  • Next scheduled tick (top-of-hour + 15) confirms green run with secrets present (current state — secrets configured 2026-04-28)
  • If a future rotation drops a secret, the hourly run will fail loudly within ≤1h instead of silently no-op-ing for days

Out of scope

  • publish-runtime.yml has a softer version of the same anti-pattern (silent cascade-skip when TEMPLATE_DISPATCH_TOKEN missing, but only on push trigger and PyPI publish has already succeeded). Flagged as separate follow-up — call is more nuanced than schedule-driven janitors.

🤖 Generated with Claude Code

Replace the soft-skip-with-warning behaviour for scheduled runs of the
hourly Cloudflare orphan sweeper with an explicit failure when the six
required secrets aren't set. Manual workflow_dispatch keeps the
soft-skip path so an operator can short-circuit a deliberate rerun
without redoing the secrets dance — they accepted the state when they
clicked the button.

Why: from some-date to 2026-04-28, all six secrets were unset on the
repo. Every hourly tick printed a yellow ::warning:: and exited 0,
which GitHub registers as "completed/success" — the sweeper was
indistinguishable from a healthy janitor with nothing to do. Cloudflare
orphans accumulated unobserved to 152/200 (~76% of the zone quota),
and only surfaced via a manual audit. The mechanism to catch this kind
of regression is to make the workflow loud: red runs prompt
investigation, green runs are presumed healthy.

Schedule/workflow_run/push paths now print three ::error:: lines
naming the missing secrets, the fix, and a one-line reference to this
incident, then exit 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged via the queue into staging with commit 6677519 Apr 29, 2026
21 checks passed
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…aged providers (#2248)

MissingKeysModal and ConfigTab both showed credential input fields for
MOLECULE_LLM_USAGE_TOKEN when a platform-managed provider was selected.
This allowed users to overwrite the provisioner-injected token.

Changes:
- MissingKeysModal: filter MOLECULE_LLM_USAGE_TOKEN from envVars when
  the selected provider is platform-managed (mirrors #2245).
  Memoized with useMemo so the array reference is stable across renders
  and does not churn the entries useEffect (Researcher review 9318).
- ConfigTab: filter the same token from required_env in the
  ProviderModelSelector onChange handler (mirrors #2245).
- Add regression test covering suppression for platform-managed vs BYOK,
  render-churn guard, and provider-switch behavior.

Fixes #2248.
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…atform-managed providers (#2388)

Researcher review RC 9320 found that ConfigTab.tsx still failed the
platform-managed-only-token case. At lines 1041-1053, required_env was only
rewritten when filteredEnvVars.length > 0 && wasTemplateDriven. If the
selected platform-managed provider's only declared env var is
MOLECULE_LLM_USAGE_TOKEN, filteredEnvVars becomes [], so the branch omitted
{ required_env: [] } and left the prior/template-driven required_env in place.

Changes:
- ConfigTab: update template-driven required_env even when the filtered list
  is empty (drop the filteredEnvVars.length > 0 guard).
- ConfigTab: carry required_env through selectorModels for registry-backed
  runtimes so wasTemplateDriven can correctly detect template-driven env
  lists (RegistryModel already had the field on the wire; expose it in the
  frontend type and map it in selectorModels).
- ProviderModelSelector: add required_env?: string[] to RegistryModel
  interface so the backend field is visible to the canvas.
- Add ConfigTab.platform-managed.test.tsx regression for the single-token
  platform provider case (+ BYOK preservation guard).

Fixes #2248.
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…ntimes/models (#2248 follow-up)

Researcher's fail-open audit found that workspace_provision.go
 (~:647, :680-695, :718-735) swallowed
providerRegistry/DeriveProvider errors and returned empty string on failure.
Then  only stamped provider when derivedProvider != "",
so a registry-KNOWN first-party runtime/model could be provisioned PROVIDERLESS
→ runtime later re-derived the WRONG provider (the moonshot→platform
NOT_CONFIGURED class).

Changes:
- deriveDefaultConfigProvider now returns (string, error) instead of string.
- Registry unavailable/load-error → propagated error (fail-closed).
- Unknown/federated runtime → preserved pass-through (providerless success).
- Known runtime + known model (exact or prefix match) + DeriveProvider error
  → propagated error (fail-closed). Mirrors llm_billing_mode.go:230-237.
- Known runtime + unregistered model (derive miss) → preserved pass-through.
- Extracted deriveDefaultConfigProviderFromManifest for unit-testability.
- Updated ensureDefaultConfig to return (map[string][]byte, error).
- Updated callers in workspace.go and org_import.go to handle errors.
- Regression tests: (a) known runtime + known model + DeriveProvider error
  → blocked; (b) unknown runtime → providerless success; (c) derive miss
  → providerless success; (d) known model success → provider stamped.

Scope: workspace_provision.go + its tests + caller plumbing only.
Branch off fresh origin/main.
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
… error + add registry-load-fail test (#2248 follow-up)

Researcher review RC 9329 on PR #2390 found two blockers:

1. org_import.go swallowed the new fail-closed config-generation error.
   In createWorkspaceTree, after the workspace DB row/layout/broadcast are
   already persisted, cfgErr only logged and continued. That left a silent
   stuck provisioning workspace during org import. Fixed by calling
   markProvisionFailed before continuing, so the canvas surfaces the failure
   card immediately.

2. Registry-unavailable/load-error fail-closed path was not covered.
   Converted providerRegistry from a named function to a variable function
   (mirrors the resolveInstanceID testability pattern) so tests can swap in
   a mock. Added TestDeriveProvider_RegistryLoadErrorFailClosed asserting
   that a registry load error blocks provisioning.

Scope still limited to workspace config/provisioning caller plumbing and tests.
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
deriveDefaultConfigProviderFromManifest references providers.Manifest,
providers.Provider, and providers.RuntimeNativeSet but workspace_provision.go
never imported the internal/providers package. This is a compile error
introduced in the #2248 follow-up branch.

Researcher review 9329 blocker #2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant