Skill: Add a user grant
Turn an access request into a reviewable Pulumi change. Every human principal is
KMS-encrypted, including IAP viewers on Cloud Run services. The change is never
applied here — a second person runs the
review-grant skill, merges, and runs pulumi up.
Read first:
infra/pulumi/README.md — the marin-iac stacks, the KMS key, and the
pulumi up prerequisites.
infra/pulumi/src/iac/gcp/iam_data.yaml header — why human user: principals
are encrypted and this file is public.
Grant surfaces
Decide which one the request needs before editing anything. A single request can
touch both.
Shared project / resource GCP IAM — a role on the hai-gcp-models project, the
KMS key, a Secret Manager secret, a GCS bucket, an Artifact Registry repo, or
a service account (who may impersonate it). Lives in
infra/pulumi/src/iac/gcp/iam_data.yaml, applied by the marin stack in
infra/pulumi. Each human user:<email> principal is KMS-encrypted once in
the principals registry; grants reference its opaque human-NNN ID.
Service accounts, groups, and domains stay plain strings.
Deploy-target IAM — runtime, secret, repository, KMS, and IAP grants for
Echo, EvalDash, Grafana, or Loom. Lives in that target's Python module under
infra/pulumi/src/iac/gcp/ and is composed into the marin stack.
Human grants reference the encrypted principal registry by opaque ID.
If you are unsure which surface a request means (e.g. "give Alice access to eval
results" could be an IAP viewer on evaldash, a roles/storage.objectViewer
grant on the record bucket, or both), ask before editing.
Collect the request
You need, per grant:
- Principal — an email for a person, or a
serviceAccount:/group:/domain:
member for automation. Only personal emails get encrypted.
- What they need access to — the specific resource, stated as a capability
("read the eval record bucket", "impersonate the ray autoscaler SA") rather
than a raw role when the requester does not know GCP roles.
- Why / for how long — a one-line justification. If the access is temporary,
note it;
GcpIamCondition can scope a grant with a CEL expiry, but prefer a
follow-up removal PR unless the requester asks for an expiry.
Translate a capability into the narrowest role that satisfies it. Reuse a role
already present in the relevant shared or deploy-target declaration for the same
resource class before reaching for a broader built-in role. If the request is vague or over-broad, ask for
specifics instead of guessing — an IAM grant is hard to walk back once applied.
Running against a GitHub issue
When invoked to respond to an issue rather than a local prompt:
- Fetch it with
gh issue view <n> --repo marin-community/marin --json title,body,comments.
- If the issue is missing a principal, the target resource, or a justification,
do not guess — post one comment (prefixed
🤖) listing exactly what you
need, and stop. Do not open a half-specified PR.
- If the request is complete, build the change and open a PR (below), then
comment on the issue linking the PR.
Register and grant the principal
For project-level roles, update the principal registry and every requested role
in one command:
uv run --package marin-iac --extra deploy \
python infra/pulumi/iam_principal.py grant alice@openathena.ai \
--project-role roles/logging.viewer \
--project-role roles/monitoring.viewer
The command decrypts existing registry entries locally to find and reuse the
person's opaque ID. It encrypts and registers the email once when the person is
new, then writes deterministic YAML. Encryption and lookup need
roles/cloudkms.cryptoKeyEncrypterDecrypter on the marin-iac key (the same
access pulumi up needs).
For a KMS key, secret, bucket, Artifact Registry repository, or service-account
grant, register the principal first:
uv run --package marin-iac --extra deploy \
python infra/pulumi/iam_principal.py register alice@openathena.ai
The command prints the existing or new human-NNN ID. Add
principal: human-NNN to a shared resource grant or
principals["human-NNN"] to a deploy-target module. Never write a personal
email in plaintext into either declaration, a commit message, or the PR body —
the repo is public.
Make the edit
Project / resource IAM — update iam_data.yaml:
- Find the grant for the target role and resource, or add one. Project
roles go in
project_grants; a bucket/secret/repo/service-account grant goes
under that resource's entry in buckets / secrets /
artifact_repositories / service_accounts (add the resource entry if it is
not there yet).
- Project-role requests are already complete after
iam_principal.py grant.
For other resource grants, add the registered principal: human-NNN
reference. Add a plain member string for service accounts, groups, domains,
workload identities, or other automation.
Deploy-target or IAP grant — add the registered principals["human-NNN"]
reference to the target's iam_grants() declaration under
infra/pulumi/src/iac/gcp/. Plain service-account, group, and domain members can
be added directly.
Verify and open the PR
./infra/pre-commit.py --files <edited files> (or --changed-files), fixing
anything it reports. git add a new file before linting so it is scoped in.
Do not run pulumi preview/up — a local preview decrypts and prints the
real emails, and applying is the reviewer's step. CI runs a redacted preview on
the PR.
Follow the commit skill to commit, push, and open the PR against main. Add
the agent-generated label. Title the PR for the capability, not the person:
[iac] Grant eval-bucket read to a new operator, never the email. The body
states the resource, the role, and the one-line justification — no personal
emails. Note in the body that a reviewer should run review-grant, then
pulumi up on the marin stack.
Assign the PR to the grant approvers so one of them picks up review-grant:
gh pr edit <n> --repo marin-community/marin \
--add-assignee yonromai,ravwojdyla,rjpower
1---2name: add-grant3description: Implement a fully specified request for a GCP IAM resource grant or Cloud Run IAP viewer in marin-iac.4---56# Skill: Add a user grant78Turn an access request into a reviewable Pulumi change. Every human principal is9KMS-encrypted, including IAP viewers on Cloud Run services. The change is never10applied here — a second person runs the11`review-grant` skill, merges, and runs `pulumi up`.1213Read first:1415- `infra/pulumi/README.md` — the marin-iac stacks, the KMS key, and the16 `pulumi up` prerequisites.17- `infra/pulumi/src/iac/gcp/iam_data.yaml` header — why human `user:` principals18 are encrypted and this file is public.1920## Grant surfaces2122Decide which one the request needs before editing anything. A single request can23touch both.24251. **Shared project / resource GCP IAM** — a role on the `hai-gcp-models` project, the26 KMS key, a Secret Manager secret, a GCS bucket, an Artifact Registry repo, or27 a service account (who may impersonate it). Lives in28 `infra/pulumi/src/iac/gcp/iam_data.yaml`, applied by the **`marin`** stack in29 `infra/pulumi`. Each human `user:<email>` principal is KMS-encrypted once in30 the `principals` registry; grants reference its opaque `human-NNN` ID.31 Service accounts, groups, and domains stay plain strings.32332. **Deploy-target IAM** — runtime, secret, repository, KMS, and IAP grants for34 Echo, EvalDash, Grafana, or Loom. Lives in that target's Python module under35 `infra/pulumi/src/iac/gcp/` and is composed into the **`marin`** stack.36 Human grants reference the encrypted principal registry by opaque ID.3738If you are unsure which surface a request means (e.g. "give Alice access to eval39results" could be an IAP viewer on evaldash, a `roles/storage.objectViewer`40grant on the record bucket, or both), ask before editing.4142## Collect the request4344You need, per grant:4546- **Principal** — an email for a person, or a `serviceAccount:`/`group:`/`domain:`47 member for automation. Only personal emails get encrypted.48- **What they need access to** — the specific resource, stated as a capability49 ("read the eval record bucket", "impersonate the ray autoscaler SA") rather50 than a raw role when the requester does not know GCP roles.51- **Why / for how long** — a one-line justification. If the access is temporary,52 note it; `GcpIamCondition` can scope a grant with a CEL expiry, but prefer a53 follow-up removal PR unless the requester asks for an expiry.5455Translate a capability into the narrowest role that satisfies it. Reuse a role56already present in the relevant shared or deploy-target declaration for the same57resource class before reaching for a broader built-in role. If the request is vague or over-broad, ask for58specifics instead of guessing — an IAM grant is hard to walk back once applied.5960### Running against a GitHub issue6162When invoked to respond to an issue rather than a local prompt:6364- Fetch it with `gh issue view <n> --repo marin-community/marin --json title,body,comments`.65- If the issue is missing a principal, the target resource, or a justification,66 **do not guess** — post one comment (prefixed `🤖`) listing exactly what you67 need, and stop. Do not open a half-specified PR.68- If the request is complete, build the change and open a PR (below), then69 comment on the issue linking the PR.7071## Register and grant the principal7273For project-level roles, update the principal registry and every requested role74in one command:7576```bash77uv run --package marin-iac --extra deploy \78 python infra/pulumi/iam_principal.py grant alice@openathena.ai \79 --project-role roles/logging.viewer \80 --project-role roles/monitoring.viewer81```8283The command decrypts existing registry entries locally to find and reuse the84person's opaque ID. It encrypts and registers the email once when the person is85new, then writes deterministic YAML. Encryption and lookup need86`roles/cloudkms.cryptoKeyEncrypterDecrypter` on the marin-iac key (the same87access `pulumi up` needs).8889For a KMS key, secret, bucket, Artifact Registry repository, or service-account90grant, register the principal first:9192```bash93uv run --package marin-iac --extra deploy \94 python infra/pulumi/iam_principal.py register alice@openathena.ai95```9697The command prints the existing or new `human-NNN` ID. Add98`principal: human-NNN` to a shared resource grant or99`principals["human-NNN"]` to a deploy-target module. Never write a personal100email in plaintext into either declaration, a commit message, or the PR body —101the repo is public.102103## Make the edit104105**Project / resource IAM** — update `iam_data.yaml`:106107- Find the grant for the target role and resource, or add one. Project108 roles go in `project_grants`; a bucket/secret/repo/service-account grant goes109 under that resource's entry in `buckets` / `secrets` /110 `artifact_repositories` / `service_accounts` (add the resource entry if it is111 not there yet).112- Project-role requests are already complete after `iam_principal.py grant`.113 For other resource grants, add the registered `principal: human-NNN`114 reference. Add a plain member string for service accounts, groups, domains,115 workload identities, or other automation.116117**Deploy-target or IAP grant** — add the registered `principals["human-NNN"]`118reference to the target's `iam_grants()` declaration under119`infra/pulumi/src/iac/gcp/`. Plain service-account, group, and domain members can120be added directly.121122## Verify and open the PR123124- `./infra/pre-commit.py --files <edited files>` (or `--changed-files`), fixing125 anything it reports. `git add` a new file before linting so it is scoped in.126- **Do not run `pulumi preview`/`up`** — a local preview decrypts and prints the127 real emails, and applying is the reviewer's step. CI runs a redacted preview on128 the PR.129- Follow the `commit` skill to commit, push, and open the PR against `main`. Add130 the `agent-generated` label. Title the PR for the capability, not the person:131 `[iac] Grant eval-bucket read to a new operator`, never the email. The body132 states the resource, the role, and the one-line justification — **no personal133 emails**. Note in the body that a reviewer should run `review-grant`, then134 `pulumi up` on the `marin` stack.135- Assign the PR to the grant approvers so one of them picks up `review-grant`:136137 ```bash138 gh pr edit <n> --repo marin-community/marin \139 --add-assignee yonromai,ravwojdyla,rjpower140 ```