Skip to content

fix(provision): share Docker+SaaS prepare path so both mint workspace secrets (RFC #2312) - #2366

Merged
HongmingWang-Rabbit merged 1 commit into
stagingfrom
auto/workspace-provision-shared
Apr 30, 2026
Merged

HongmingWang-Rabbit merged 1 commit into
stagingfrom
auto/workspace-provision-shared

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

Root cause of 2026-04-30 silent-503 chat-upload bug: provisionWorkspaceCP (SaaS) skipped issueAndInjectInboundSecret while provisionWorkspaceOpts (Docker) called it. Every prod SaaS workspace shipped with NULL platform_inbound_secret → upload returned 503 with the v2-enrollment message on every attempt.

Structural fix — extract the mode-agnostic prepare/mint/markFailed helpers into workspace_provision_shared.go so both modes call exactly the same prelude. Adding a future provision-time setup step now goes in ONE place and both modes pick it up automatically.

What's shared

  • prepareProvisionContext — secrets load (global+workspace), env mutators, identity injection, missing-env preflight, cfg build
  • mintWorkspaceSecretsauth_token + platform_inbound_secret (load-bearing for chat upload + every /internal endpoint)
  • markProvisionFailed — broadcast WORKSPACE_PROVISION_FAILED + persist last_sample_error

What stays mode-specific

Drift prevention

  • TestProvisionFunctions_AllCallMintWorkspaceSecrets — AST gate that walks every provision* function in the package and asserts it calls mintWorkspaceSecrets. Same shape as the audit-coverage gate from fix(security): scope PausePollersForToken to requesting workspace (#329) #335. New provision paths must call mint or be added to provisionExemptFunctions with a one-line justification
  • TestMintWorkspaceSecrets_PersistsInboundSecretInSaaSMode — behavioral test that pins the contract: SaaS mode MUST persist platform_inbound_secret to the DB column even though it skips Docker file injection

Existing-workspace recovery (lazy-heal)

chat_files.go Upload + Download handlers detect NULL platform_inbound_secret and call IssuePlatformInboundSecret inline, returning 503 with retry_after_seconds=30. Self-heals workspaces that were provisioned before this fix without requiring a destructive reprovision.

Test plan

  • go build ./... clean
  • go test ./... all green (full workspace-server module)
  • AST gate verified to fire red on deliberate violation (commented-out mint call surfaces the exact function name + actionable remediation)
  • TestProvisionWorkspace_NoInternalErrorsInBroadcast and TestProvisionWorkspaceCP_NoInternalErrorsInBroadcast still pin the F1086/[QA] err.Error() leaks in workspace_provision.go and plugins_install_pipeline.go #1206 redaction invariant
  • Hand-test on staging once merged: deprovision + reprovision a SaaS workspace, confirm platform_inbound_secret row is NOT NULL, confirm chat upload returns 200
  • Hand-test lazy-heal: pick an existing workspace with NULL secret, attempt upload, confirm 503 + retry_after, confirm subsequent upload returns 200

🤖 Generated with Claude Code

… secrets (RFC #2312)

Root cause of 2026-04-30 silent-503 chat-upload bug: provisionWorkspaceCP
(SaaS) skipped issueAndInjectInboundSecret while provisionWorkspaceOpts
(Docker) called it. Every prod SaaS workspace provisioned with NULL
platform_inbound_secret → upload returned 503 with the v2-enrollment
message on every attempt.

Structural fix:
- Extract prepareProvisionContext (secrets load, env mutators, preflight,
  cfg build), mintWorkspaceSecrets (auth_token + platform_inbound_secret),
  markProvisionFailed (broadcast + DB update) into workspace_provision_shared.go
- Refactor both provision modes to call the shared helpers
- Add provisionAbort struct so the missing-env failure class can carry its
  structured "missing" payload through the shared abort path
- Unify last_sample_error: previously the decrypt-fail path skipped it while
  others set it; users now see every failure class in the UI

Drift prevention:
- AST gate TestProvisionFunctions_AllCallMintWorkspaceSecrets asserts every
  function in the provisionFunctions set calls mintWorkspaceSecrets at least
  once (same shape as the audit-coverage gate from #335). New provision paths
  must either call mint or be added to provisionExemptFunctions with a
  one-line justification
- Behavioral test TestMintWorkspaceSecrets_PersistsInboundSecretInSaaSMode
  pins the contract: SaaS mode MUST persist platform_inbound_secret to the DB
  column even though it skips file injection

Existing-workspace recovery (chat_files.go lazy-heal):
- Upload + Download handlers detect NULL platform_inbound_secret and call
  IssuePlatformInboundSecret inline, returning 503 with retry_after_seconds=30
- Self-heals workspaces that were provisioned before this fix without
  requiring destructive reprovision

Tests: full handlers + workspace-server module green; AST gate verified to
fire red on deliberate violation (commented-out mint call surfaces the
exact function name + actionable remediation message).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit
HongmingWang-Rabbit added this pull request to the merge queue Apr 30, 2026
Merged via the queue into staging with commit b9b0a46 Apr 30, 2026
19 checks passed
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the auto/workspace-provision-shared branch April 30, 2026 09:25
HongmingWang-Rabbit pushed a commit that referenced this pull request Apr 30, 2026
…#2367)

Closes #2367.

TeamHandler.Expand provisioned child workspaces by directly calling
h.provisioner.Start, skipping mintWorkspaceSecrets and every other
preflight (secrets load, env mutators, identity injection, missing-env,
empty-config-volume auto-recover). Children shipped with NULL
platform_inbound_secret + never-issued auth_token — same drift class as
the SaaS bug just fixed in PR #2366, found while exercising a stronger
gate against this package.

Fix:

- TeamHandler now holds *WorkspaceHandler. Expand delegates each child
  provision to wh.provisionWorkspace, picking up the shared
  prepare/mint/preflight pipeline automatically. Future provision-time
  steps go in ONE place and team-expand inherits them.
- prepareProvisionContext gains PARENT_ID env injection sourced from
  payload.ParentID (which Expand now populates). This preserves the
  signal workspace/coordinator.py reads on startup, without threading
  env through provisioner.WorkspaceConfig manually.
- NewTeamHandler signature gains *WorkspaceHandler; router passes it.

Gate upgrade:

- TestProvisionFunctions_AllCallMintWorkspaceSecrets is now
  behavior-based: it walks every FuncDecl in the package and flags any
  function that calls h.provisioner.Start or h.cpProv.Start without
  also calling mintWorkspaceSecrets. Drift-resistant by construction —
  a future provision function with any name still trips the gate.
- Replaces the name-list version from PR #2366. The name list missed
  Expand precisely because Expand wasn't named provision*; the
  behavior-based detector caught it spontaneously when prototyped.

Tests: full workspace-server module green; gate previously verified to
fire red on Expand pre-fix and on deliberate mintWorkspaceSecrets
removal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
…2366/#588)

The merge queue used hard-coded limit=50 on list endpoints, silently
truncating enumeration when more than 50 open PRs or status checks exist.
This meant newer PRs could be invisible to the queue, and PRs with >50
status contexts would have incomplete check evaluation.

Changes:
- Add api_paginated() helper that loops through pages until a partial
  page is returned (indicating end of collection).
- list_queued_issues() and list_candidate_issues() now use pagination
  to enumerate ALL open PRs, not just the first 50.
- get_combined_status() /statuses enrichment now paginates to capture
  all status checks beyond the 50-entry cap.

All 52 gitea-merge-queue tests pass.

Refs: molecule-core#2366/#588, PM dispatch 01eaa317.
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
… issues, statuses (#2366/#588)' (#2367) from fix/gitea-merge-queue-pagination into main

2 genuine officials current head + required-green, mergeable — direct-merge (avoid rebase-churn approval-dismissal). CTO diff-reviewed (efficiency unblock).
HongmingWang-Rabbit pushed a commit that referenced this pull request Jun 12, 2026
Serialized merge by gitea-merge-queue after current-main, genuine approvals, and required CI checks were green.
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