PayCargo · AI-Sandbox · Step 6.t

6.tSetup cartridge: operator-facing wizard for foundation configuration

A Console-native setup surface modeled on GitBook-style guided instructions. Every foundation concern (SSO, DNS, OAuth providers, certs) gets a wizard: shows operator current state, surfaces the values to hand off, captures the return values, generates the handoff messages. Doctrine + Step docs remain the engineering-depth reference; this is the operator UX layered on top.
Block: 6.t (Console doctrine; companion to 6.p SSO, 6.n OAuth, 6.s CMS) · State: Scoping draft. Decisions in section 9. · Cartridge: cartridges/global/setup/
Scoping
Today, an operator configures SSO by reading Step 6.p, running terraform output, copying values into an email, and sending it to the Azure admin. Same shape for DNS (Slack messages), Salesforce Connected App (Howto.docx), and every future provider. The Setup cartridge consolidates these flows into one wizard surface so the operator doesn't dig through doctrine docs to find what to copy.

Sections

  1. Why a Setup cartridge
  2. Flows it covers
  3. Flow state machine
  4. The copy-out / paste-back pattern
  5. Audience and auth
  6. Relationship to the CMS
  7. Relationship to tfvars + apply
  8. Architecture
  9. Decisions to confirm
  10. Ship plan

1. Why a Setup cartridge

The current setup loop for a single foundation concern:

  1. Operator reads the relevant Step doc (6.p for SSO, 6.n for OAuth, etc.).
  2. Runs terraform output -raw <output-name> for the values the outside admin needs.
  3. Drafts a Slack message or email with the values + the prereq checklist.
  4. Outside admin replies with their return values (metadata URL, client_id, etc.).
  5. Operator edits tfvars or Secrets Manager with the return values.
  6. Runs terraform apply.
  7. Verifies the configuration is live.

This works. It also has six problems:

The Setup cartridge addresses all six by giving operators a single, navigable list of foundation setups, each with its current state, the values to hand off, the inputs to capture, and the verification step. Same pattern GitBook uses for setup-heavy products.

2. Flows it covers

v1 ships the flows we already have doctrine for. Each flow has a wizard with stages.

FlowOutside partyWhat the operator hands offWhat they get back
Azure SSO PayCargo Azure admin ACS URL, Entity ID, prereq checklist (Step 6.p section 12) SAML metadata URL, confirmation of conditional access MFA
DNS records PayCargo Systems / DNS admin 3 batches of DNS records (host CNAMEs, ACM validation, SES records) Confirmation that records are added
Salesforce Sandbox PayCargo Salesforce admin Callback URLs, the Howto.docx walking through Connected App setup Consumer Key, Consumer Secret (stored in Secrets Manager via the cartridge)
Salesforce Production Same admin, in production org Same shape as Sandbox, different callback paths Same shape
Custom domain certs PayCargo DNS admin ACM validation CNAMEs Confirmation, then automated cert issuance
SES domain PayCargo DNS admin SES verification + DKIM records Verification confirmation

Future flows (when their doctrine lands):

3. Flow state machine

Each flow goes through stages. The cartridge tracks state in DDB so the operator opens the cartridge and immediately sees where every flow is.

┌─────────────┐ │ not-started │ ──── operator opens the wizard ────► └─────────────┘ │ ▼ ┌──────────────────┐ │ inputs-displayed │ values surfaced (ACS URL, Entity ID, ...) └──────────────────┘ │ │ operator clicks "Send handoff to Azure admin" │ (copies the formatted message to clipboard or opens email) ▼ ┌──────────────────┐ │ handoff-sent │ cartridge logs WHO sent it WHEN └──────────────────┘ │ │ outside admin replies with return values │ operator pastes them into the cartridge form ▼ ┌──────────────────┐ │ inputs-captured │ return values stored (SSM, Secrets Manager, or └──────────────────┘ tfvars snippet generated for paste) │ │ Terraform applies the new config ▼ ┌──────────────────┐ │ provisioned │ IdP exists, SAML active └──────────────────┘ │ │ operator clicks "Verify" │ cartridge runs an automated check (sign-in test, API call) ▼ ┌──────────────────┐ │ verified │ flow is done └──────────────────┘

Each transition is logged. The operator gets a stage badge per flow: green when verified, amber for in-flight, gray for not-started. The cartridge home page is a list of flows with their stage badges.

4. The copy-out / paste-back pattern

The single most important UX element. Modeled on how operators actually use the doctrine today (manually copying outputs into Slack).

Copy-out (handoff to outside admin)

Each handoff has:

Mock: SSO handoff card

┌────────────────────────────────────────────────────────────────┐
│ Send to Azure admin                                            │
│                                                                │
│ Subject: PayCargo Forge SAML configuration                     │
│                                                                │
│ Hi,                                                            │
│                                                                │
│ Please configure the PayCargo Forge enterprise application in  │
│ Azure with the following SAML service provider details:        │
│                                                                │
│   ACS URL:    https://ai-sandbox-obs-app-95922820.auth         │
│               .us-east-1.amazoncognito.com/saml2/idpresponse   │
│   Entity ID: urn:amazon:cognito:sp:us-east-1_fJLnhjqnl         │
│                                                                │
│ The full prereq checklist (scopes, conditional access policy,  │
│ etc.) is attached.                                             │
│                                                                │
│ Please return: SAML metadata URL                               │
│                                                                │
│ Thanks,                                                        │
│ Mike Tang                                                      │
│                                                                │
│   [Copy as Slack]  [Copy as email]  [Download Howto.docx]      │
│   [Mark as sent]                                               │
└────────────────────────────────────────────────────────────────┘

Paste-back (capture return values)

Each return value is a labeled input field with:

Mock: SSO return form

┌────────────────────────────────────────────────────────────────┐
│ Capture Azure admin's response                                 │
│                                                                │
│ SAML metadata URL:                                             │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ https://login.microsoftonline.com/.../federationmetadata/. │ │
│ └────────────────────────────────────────────────────────────┘ │
│   [Test fetch]   (fetches the URL, validates XML, parses Entity)│
│                                                                │
│ Conditional access MFA on the app:  ○ Confirmed  ● Not yet     │
│                                                                │
│   [Save inputs]   (writes to SSM, generates Terraform snippet) │
└────────────────────────────────────────────────────────────────┘

5. Audience and auth

System admins only. Cognito group admins. Same gate as the obs Admin tab.

Federated SSO users without admin group cannot see this cartridge in the Console launcher (it doesn't appear on their tile grid). Direct URL access returns 403 from the cartridge backend.

The Setup cartridge is the system admin's operating surface. Doctrine 8.8 says system admins are Cognito-native; the Setup cartridge runs under that authority. SSO users do not configure the platform; they request access to it via the Request Roles cartridge.

6. Relationship to the CMS

The instructions inside each wizard (the "what the outside admin needs to do" text) are content. They belong in the CMS (Step 6.s) when CMS lands.

Until then, instructions ship as files inside the Setup cartridge code (similar to how the chatbot ships its system prompt). Each flow has a markdown file with the prereq narrative; the cartridge renders it as the wizard's middle pane.

When CMS lands (per Step 6.s):

7. Relationship to tfvars + apply

Three patterns for where return values land. Each flow picks one.

PatternWhen to use itWrite targetApply flow
SSM parameter Non-secret config Terraform reads at plan time (e.g., SSO metadata URL, OAuth callback URLs) /forge/setup/<flow>/<input> Terraform reads SSM as a data source; next apply picks up the change. Operator triggers apply via the existing Terraform Apply workflow.
Secrets Manager Secret values (OAuth client_secret, API keys) /forge/oauth/clients/<provider> etc. Terraform creates the secret resource; the Setup cartridge writes the value. Apply doesn't re-roll the secret.
Tfvars snippet (display-only) Configuration that genuinely belongs in version control (feature flags, regional toggles) Cartridge generates the snippet and shows it to the operator Operator copies the snippet into the repo's tfvars file, opens a PR, applies via CI.

The Setup cartridge knows which pattern each flow uses and surfaces the right UX. For SSM and Secrets Manager patterns, "Save inputs" writes immediately. For the tfvars pattern, "Save inputs" generates a copy-pasteable snippet and a "Open PR with this change" link to GitHub.

The SSO case

SSO uses Pattern 1 (SSM). After Azure admin returns the metadata URL:

  1. Operator pastes URL into the Setup cartridge form.
  2. Cartridge writes /forge/sso/azure-metadata-url in SSM.
  3. Cartridge writes /forge/sso/enabled = true in SSM.
  4. Operator clicks "Apply Terraform" in the cartridge.
  5. Cartridge triggers the existing Terraform Apply workflow via GitHub workflow_dispatch (same way operators do today).
  6. Terraform reads SSM at plan time, flips sso_enabled = true, applies the IdP resource.
  7. Cartridge polls Cognito to verify the IdP exists; flips flow state to provisioned.
  8. Operator clicks "Verify"; cartridge does a SAML test sign-in; flips to verified.

Terraform variables read SSM via data source

This is the small wiring change needed: console/sso.tf reads sso_enabled and sso_metadata_url from SSM data sources instead of via input variables. That makes the Setup cartridge's writes effective on next apply. The current PR 95/96 wiring uses input variables; PR 4 of this ship plan (section 10) updates it.

8. Architecture

Standard cartridge shape. API Gateway HTTP API + Lambda + DDB + CloudFront-fronted S3 frontend.

cartridges/global/setup/
  backend.tf                        Lambda + API Gateway + IAM
  frontend.tf                       S3 + CloudFront
  auth.tf                           Cognito client (admin group required)
  cartridge.tf                      Manifest

  lambda/
    handler.js                      Routes for each flow
    flows/
      sso.js                        SSO flow handler (state machine)
      dns.js                        DNS flow handler
      salesforce.js                 Salesforce setup flow handler
      ...
    state.js                        DDB state read/write

  frontend/
    index.html                      Cartridge home (flow list)
    setup-sso.html                  SSO wizard
    setup-dns.html                  DNS wizard
    setup-salesforce.html           Salesforce wizard
    js/
      wizard.js                     Shared wizard component (uses SDK Modal + Table)
      copy-out.js                   Copy-out card behavior
      paste-back.js                 Paste-back form behavior

  content/                          (until CMS lands)
    sso-prereqs.md                  Step 6.p section 12 in markdown
    dns-instructions.md             DNS admin instructions
    salesforce-howto.md             Salesforce Connected App walkthrough

DDB schema

Table: forge-setup-state

  pk: "flow"
  sk: flow_id           // e.g. "sso-azure", "dns-paycargo-org",
                        //      "salesforce-sandbox", "salesforce-production"

  Attributes:
    status:             // "not-started" | "inputs-displayed" | "handoff-sent" |
                        //   "inputs-captured" | "provisioned" | "verified"
    handoff_sent_by:    // cognito username
    handoff_sent_at:    // ISO timestamp
    inputs_captured_by: // cognito username
    inputs_captured_at: // ISO timestamp
    inputs:             // JSON map of return values (non-secret)
    provisioned_at:     // ISO timestamp (when TF apply succeeded)
    verified_at:        // ISO timestamp
    last_check_status:  // "ok" | "failed" | "pending"

9. Decisions to confirm

Decision 1

Write target for SSO inputs

Proposed: SSM parameters (Pattern 1). Setup cartridge writes /forge/sso/enabled and /forge/sso/metadata-url. Terraform reads via data sources on next apply. Requires small change to console/sso.tf in PR 4 of the Setup cartridge ship plan.

Decision 2

Apply trigger

Proposed: Cartridge triggers the existing Terraform Apply workflow via GitHub workflow_dispatch. Same gating as today (manual APPLY confirm). The cartridge passes the trigger; the human still types the confirmation in GitHub Actions UI.

Decision 3

v1 flow scope

Proposed: SSO + DNS + Salesforce Sandbox in v1. Salesforce Production, GitHub, GitBook, Monday in v1.5 as additional flows after the pattern is validated. Each new provider is one new flow file; cheap to add.

Decision 4

Content source (instructions)

Proposed: files in the cartridge until CMS lands; CMS-served after. The Setup cartridge code v1.0 ships with markdown files. When the CMS cartridge (Step 6.s) is live, a small refactor swaps the read source.

Decision 5

Folder location

Proposed: cartridges/global/setup/. Setup is a platform concern, not a department concern. Lives under global/ alongside the chatbot and the future Request Roles + CMS + MCP cartridges.

Decision 6

Verification mechanism per flow

Proposed: per-flow automated check. For SSO: cartridge attempts a SAML test sign-in. For DNS: cartridge does a DNS lookup. For Salesforce: cartridge makes a test OAuth flow. Each flow declares its own verify function.

10. Ship plan

  1. PR 1: this doc + Doctrine 2.11. Decisions locked. No code. New doctrine entry: "Operator setup is a cartridge flow, not a tfvars edit."
  2. PR 2: cartridge skeleton. cartridges/global/setup/ with backend (Lambda + API Gateway + DDB + IAM), frontend (S3 + CloudFront), Cognito admin gate, manifest. Empty flow list (no flows implemented yet).
  3. PR 3: shared wizard frontend components. wizard.js, copy-out.js, paste-back.js. Built on SDK v1 components. Generic enough to be reused per flow.
  4. PR 4: SSO flow (first concrete flow). flows/sso.js, setup-sso.html, content/sso-prereqs.md. Updates console/sso.tf to read from SSM data sources instead of input variables. End-to-end test: operator clicks through, writes SSM, triggers apply, verifies.
  5. PR 5: DNS flow. flows/dns.js + content. Reads from the domains module outputs; surfaces the 3 batches of records.
  6. PR 6: Salesforce Sandbox flow. Includes Howto.docx generation, callback URL handoff, Consumer Key + Secret capture, write to Secrets Manager.
  7. PR 7: add Setup tile to Console launcher. Visible only to admin group.

v1 ships after PR 7. Subsequent flows (Salesforce Production, GitHub, GitBook, Monday, etc.) are individual PRs adding one flows/<name>.js + content file + frontend page each.

What this changes about the in-flight SSO work

PR 95/96 of Step 6.p declared SSO at the Console layer with input variables. PR 4 of this Setup cartridge ship plan refactors that to read from SSM data sources instead. Same end state; just rewired so the Setup cartridge can write the values. The PR 95/96 outputs (console_sso_acs_url, console_sso_entity_id) remain valid; the cartridge will read them too.

References