|
1 | 1 | # `@vozka/cli init` — per-account base bring-up (design) |
2 | 2 |
|
3 | | -Status: **Phase A built** (`@vozka/cli`, `vozka init <account>`); **Phase B pending** the propustka refactor. |
4 | | -Supersedes the `scripts/bootstrap-*.ts` wizard (kept as legacy until the CLI is proven). Decided 2026-06-26 |
5 | | -with the operator. |
| 3 | +Status (2026-06-27): **Phase A built** + **Phase B landed except Stage 1 wiring**. propustka native-auth is |
| 4 | +published (`@propustka/* 0.0.6`) WITH the seed-from-env primitive (`PROPUSTKA_PROVISIONING_KEY`); vozka is |
| 5 | +co-versioned onto it — `reconcileAccess`/`AppAccess` dropped, `reconcileSchema({ adminKey })`, the worker |
| 6 | +auth migrated to `PropustkaAuth`, the key collapsed to one `px_`, and `@vozka/cli` generates it. The ONE |
| 7 | +remaining Phase B item is **wiring Stage 1 (propustka's own deploy) into `platform.yml`** — deferred to the |
| 8 | +account bring-up (#24) because it needs propustka's OIDC + signing config provisioned into the Environment. |
| 9 | +Supersedes the `scripts/bootstrap-*.ts` wizard (kept as legacy until the CLI is proven). Decided 2026-06-26. |
6 | 10 |
|
7 | 11 | ## Goal |
8 | 12 |
|
@@ -59,32 +63,56 @@ by BOTH stages — no minting, no propustka checkout, no branch-mismatch. |
59 | 63 | | **propustka (Stage 1)** | reads it from env and **idempotently upserts** it as an admin/provisioning credential at deploy — the machine analog of `PROPUSTKA_BOOTSTRAP_ADMINS`. _(= the seed-from-env primitive to add in the propustka refactor.)_ | |
60 | 64 | | **vozka (Stage 2)** | reads the **same** key and authenticates its `reconcileSchema`/`reconcileAccess` calls with it | |
61 | 65 |
|
62 | | -### ⚠️ Key SHAPE is the one open item — depends on the new propustka auth model |
63 | | - |
64 | | -- **Old model:** `PROPUSTKA_CLIENT_ID` + `PROPUSTKA_CLIENT_SECRET` (Access service-token / client-credentials |
65 | | - pair). vozka reads both today. |
66 | | -- **New model (refactor):** credentials are single `px_` bearer keys (`issueKey`/`mintFromKey`). Then the |
67 | | - provisioning key is likely **one `px_` token**, not an id/secret pair — e.g. env `PROPUSTKA_PROVISIONING_KEY`. |
68 | | - The CLI generates `px_<random>`; propustka Stage 1 upserts a credential whose hash = hash(token) under a |
69 | | - stable identity (so re-deploy is a no-op / rotation); vozka presents the token as a bearer. |
70 | | -- **Co-version implication:** if provisioning auth collapses to a single key, vozka's `@propustka/client` |
71 | | - reconcile auth changes from `(client_id, client_secret)` to one key — that's part of the |
72 | | - `@propustka/* ^0.0.5 → ^0.0.6` bump (the co-versioned suite). Confirm the exact surface against the new |
73 | | - `@propustka/client`. |
74 | | - |
75 | | -**To finalize the seeded-key env contract + the vozka co-version change, I need:** the new propustka |
76 | | -provisioning auth shape (single `px_` key vs id/secret), the seed-from-env env name propustka will read, and |
77 | | -the `@propustka/client` reconcile auth signature in the new version. |
| 66 | +### Key SHAPE — RESOLVED (2026-06-27, against propustka `feat/propustka-native-auth`) |
| 67 | + |
| 68 | +- **Single `px_` bearer.** A propustka credential is ONE opaque `px_<random>` token (SHA-256-hashed in the |
| 69 | + `credentials` table, plaintext returned once by `issueKey`). NOT an id/secret pair. So the provisioning key |
| 70 | + is one token in **one** env var (proposed `PROPUSTKA_PROVISIONING_KEY`), not `PROPUSTKA_CLIENT_ID`/`_SECRET`. |
| 71 | +- **Reconcile auth = `adminKey` bearer.** `reconcileSchema({ url, app, schema, adminKey })` sends |
| 72 | + `Authorization: Bearer ${adminKey}` (the `px_`). vozka passes the single provisioning key as `adminKey`. |
| 73 | +- **No `reconcileAccess` / no `AppAccess`.** CF Access is fully removed; the old `reconcileAccess` + |
| 74 | + `AppAccess`/`AccessAppDecl`/`AccessRule` are DELETED from `@propustka/*`. Per-path gating is now `AppGates`, |
| 75 | + **pure runtime SDK config consumed by `PropustkaAuth` in each app — NOT reconciled at deploy** and not a |
| 76 | + vozka concern. So vozka's whole "reconcile access" step + the `access` field on `defineApp` go away. |
| 77 | +- **`IamClient.authenticate(request)` is gone** (caller resolution is now server-side `resolveCaller`); the |
| 78 | + app-side request-auth surface is `PropustkaAuth`. vozka's control-plane worker auth must migrate to it. |
| 79 | + |
| 80 | +### The ONE missing primitive on the propustka side — seed-from-env |
| 81 | + |
| 82 | +propustka has `IAM_BOOTSTRAP_ADMINS` (admit a human by email at resolution time) but **no machine analog**: |
| 83 | +nothing reads a `px_` from env and admits it as a provisioning admin. Recommended (idiomatic, no DB writes): |
| 84 | +in `worker/src/auth.ts resolveCaller`, alongside the local-dev bypass + the bootstrap-admin email check, add a |
| 85 | +`PROPUSTKA_PROVISIONING_KEY` env — when a presented bearer's hash equals `hashToken(env)`, resolve a synthetic |
| 86 | +global-admin caller (`permissions: [{ action: '*', scope: null, source: 'bootstrap' }]`), no `credentials` |
| 87 | +row. Idempotent by construction (env compare), rotatable (change the env), no chicken-and-egg, no migration. |
| 88 | + |
| 89 | +**Still required from propustka before vozka Phase B can land:** (1) the seed-from-env primitive above; |
| 90 | +(2) bump `@propustka/core` + `@propustka/client` 0.0.5 → 0.0.6 and **publish** (the branch is unmerged, |
| 91 | +unpublished — vozka on `^0.0.5` still resolves the OLD model). |
78 | 92 |
|
79 | 93 | ## Build phases + dependencies |
80 | 94 |
|
81 | 95 | - **Phase A — propustka-agnostic CLI plumbing (buildable now).** `packages/cli` skeleton; `init` flow; CF |
82 | 96 | token → account/zones; repo scaffold from templates; GitHub App (manifest flow, public-when-cross-org — |
83 | 97 | already wired in `f58b910`); GitHub Environment + secrets/vars; `.env`; trigger. Treats the provisioning |
84 | 98 | key as an opaque env value, so it needs nothing from propustka. |
85 | | -- **Phase B — seeded key + propustka Stage 1 + vozka co-version (after the propustka refactor lands).** |
86 | | - Finalize the key shape; wire Stage 1 (propustka deploy + seed) into `platform.yml`; bump vozka's |
87 | | - `@propustka/*` pin + adapt reconcile auth; drop any temporary escape. |
| 99 | +- **Phase B — seeded key + propustka Stage 1 + vozka co-version (after propustka publishes 0.0.6).** This is |
| 100 | + BIGGER than "finalize the key shape" — the native-auth refactor reshapes the whole reconcile + auth surface. |
| 101 | + All of it is coupled to the `@propustka/* ^0.0.5 → ^0.0.6` bump (it won't typecheck against 0.0.5), so it |
| 102 | + lands as one change: |
| 103 | + 1. **Drop `reconcileAccess` entirely** (`core/deploy.ts`, `core/runtime.ts`): no successor — gates are |
| 104 | + runtime SDK config. Remove `ReconcileAccessError`, `AccessReconciler`, the access reconcile call. |
| 105 | + 2. **`reconcileSchema` auth** `{ clientId, clientSecret }` → `{ adminKey }` (one `px_` bearer). |
| 106 | + 3. **Config surface** (`vozka-config`): drop `AppAccess`/`AccessAppDecl`/`AccessRule` import + re-export and |
| 107 | + the `access` field on `defineApp`; per-app configs (poplach/revizor) drop their `access` block. |
| 108 | + 4. **Worker auth seam** (`worker/iam.ts`): `IamClient.authenticate(request)` is gone → migrate to |
| 109 | + `PropustkaAuth`; rework `BootstrapAdminAuthContext`/the guard. |
| 110 | + 5. **Key threading**: collapse `PROPUSTKA_CLIENT_ID` + `_SECRET` → one `PROPUSTKA_PROVISIONING_KEY` across |
| 111 | + `core/cli.ts`, `runner/protocol.ts`, `worker/run-lifecycle.ts`, `worker/env.ts`. |
| 112 | + 6. **@vozka/cli**: generate one `px_` (not `{clientId, clientSecret}`); one Environment secret |
| 113 | + `PROPUSTKA_PROVISIONING_KEY`; update `init.ts` + `templates/platform.yml` + README. |
| 114 | + 7. **Pin bump** `^0.0.5 → ^0.0.6` in every package.json + `docker/package.json`. |
| 115 | + 8. **Wire Stage 1** propustka deploy into `platform.yml` (propustka already ships a `vozka.config.ts`). |
88 | 116 |
|
89 | 117 | ## Out of scope / later |
90 | 118 |
|
|
0 commit comments