# Contributing to the ops-webapp Pulumi stack

## Bootstrap vs Pulumi ownership

Bootstrap and Pulumi have distinct ownership boundaries. Mixing them creates
circular dependencies that block first deploys.

**Bootstrap** covers one-time manual steps that must run before `pulumi up`.
These are documented as numbered steps in
[`BOOTSTRAP.md`](./BOOTSTRAP.md) — for example,
[Step 6](./BOOTSTRAP.md) creates the ops-webapp OAuth secret container, grants
the runtime SA access, and adds the initial secret version.

**Pulumi** covers declarative infrastructure defined in
[`__main__.py`](./__main__.py). For example,
[`define_mcp_cloud_run_service()`](./__main__.py) is the factory that provisions
each MCP Cloud Run service, wiring env vars, scaling, health checks, and IAM
invoker grants.

Bootstrap owns:

- Artifact Registry repositories
- Bootstrap container images
- Secret Manager secret containers (`gcloud secrets create`)
- Secret values (`gcloud secrets versions add`)
- Secret IAM grants (`gcloud secrets add-iam-policy-binding`)

Pulumi owns:

- Cloud Run services
- Serverless NEGs and load balancer routing
- DNS records and TLS certificates
- Service accounts
- API enablement

**Design principles**:

1. Initial service account grants and secret creation operations generally are
   best to live within the bootstrap setup steps.
2. We generally try to avoid giving god mode access to the service account
   running Pulumi Apply. So granting it unlimited access to grant itself more
   access is necessarily something to avoid.
3. We try to avoid circular dependencies between the Bootstrap script and the
   Pulumi Apply. We don't want a scenario where bootstrap code needs to run, in
   order for Pulumi apply to run, in order for more bootstrap code to run, in
   order for Pulumi Apply to finish running.
4. We want code-only deployments to run smoothly without needing to rerun
   `pulumi apply`. For this reason, container build and deploy operations, such
   as those needed by Google Cloud Run, are often decoupled from the infra
   module entirely, running upon a trigger from the source code of the
   application instead of changes to the infra IAC codebase.

For secrets specifically, Pulumi must use read-only lookups
(`gcp.secretmanager.get_secret()`) — never `gcp.secretmanager.Secret()`:

```python
# CORRECT — read-only lookup of a bootstrap-managed secret
secrets[SECRET_ID] = gcp.secretmanager.get_secret(
    secret_id=SECRET_ID,
    project=PROJECT,
)

# WRONG — creates the secret, causing circular dependency
secrets[SECRET_ID] = gcp.secretmanager.Secret(
    SECRET_ID,
    secret_id=SECRET_ID,
    project=PROJECT,
    replication=...,
)
```

## Adding a new secret

1. Add the secret creation, IAM grant, and initial version to `BOOTSTRAP.md`
   under Stage 0.
2. In `__main__.py`, add a `get_secret()` lookup in `define_secrets()`.
3. Reference the secret ID in the Cloud Run service env configuration.
4. Run the bootstrap steps, then `pulumi up`.

## Adding a new Cloud Run service

Follow the existing `define_mcp_cloud_run_service()` pattern:

1. Define constants (service name, container image, public URL).
2. Add bootstrap steps for the AR repo, image, and any secrets.
3. Call the factory function in `main()`.
4. Add a path route rule if path-based routing is needed.
