---
id: w4/m4/t005
title: "Seed tuples at deploy (bootstrap → admin) + E2E allow/deny on mock"
worker: worker4
status: done
estimate: 35m
depends_on: [w4/m4/t004]
---

## Objective

Make the flip safe and prove it end-to-end: seed the relation tuples that keep the platform operable (`bex-bootstrap` → `admin` of `tenant:default`) at deploy time, then extend the E2E to show a seeded identity passing, an unseeded fresh key getting 403, and the unset-env regression staying allow-all.

## Context

- Seeding belongs with the other out-of-band deploy steps: extend `scripts/authz-model.sh` (or a sibling `authz-seed.sh`) to write the tuple idempotently (`POST /stores/{id}/write` with duplicate-tolerant handling); deploy.yml runs it right after the model apply.
- E2E: `scripts/auth-e2e.sh` already builds bex-api on the host with env pointing at port-forwards — add a third act: run bex-api with `BEX_OPENFGA_URL` set (port-forward openfga), assert bootstrap token passes all verbs, a freshly minted key (no tuples) gets 403 on `POST /v1/api-keys` + suspend but may read (or 403 everywhere — assert whatever the t004 map says), then re-run without the env and assert the fresh key passes again (regression).
- Mock-cluster quirks: pin OpenFGA to the control-plane node like everything else (docs/ADR012-auth.md local notes).

## Steps

1. Idempotent tuple seeding (script + deploy.yml step ordering: model → tuples → operator wait).
2. Extend `scripts/auth-e2e.sh` with the authz act (allow / deny / env-unset regression).
3. Full run on the mock cluster; record the invocation in this milestone's README.

## Files

- `scripts/authz-model.sh` (or `scripts/authz-seed.sh`, new)
- `.github/workflows/deploy.yml`
- `scripts/auth-e2e.sh`

## Acceptance criteria

- [ ] E2E exits 0 on mock: seeded bootstrap allowed, unseeded key 403 on manage verbs, env-unset run behaves exactly like pre-m4.
- [ ] Seeding runs twice without error or duplicate tuples.

## Out of scope

- Tenant self-service tuple management (needs w1/m2 accounts); UI.
