You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(cli,mcp): run402 auth scaffold-roles — role-table + requireRole gate generator
Offline, deterministic generator (CLI subcommand + scaffold_roles MCP tool) that emits the conventional app_roles(user_id uuid, role text) migration, a matching requireRole deploy-spec gate snippet, and a first-operator service-role bootstrap INSERT. No project/network. Docs (cli/llms-cli.txt, SKILL.md) document auth.role() (@run402/functions 3.4.0) and the requireRole(x in allowed) vs auth.role() (branch-friendly read) distinction, plus the tenant-user-id (not wallet) keying. Implements the public-repo follow-ups for the improve-role-gate-ergonomics change. 339 CLI tests pass; new files typecheck clean (pre-existing unrelated tsc error in src/tools/jobs.ts re: Jobs.downloadArtifact SDK lag).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: register scaffold_roles + auth:scaffold-roles in the sync surface map
Adds the auth_scaffold_roles capability to SURFACE (mcp/cli/openclaw) and SDK_BY_CAPABILITY (null — offline generator, no SDK method) so the MCP/CLI/OpenClaw inventory guards pass. The remaining sync.test.ts failure (SDK mapping → real method) is the pre-existing jobs.ts downloadArtifact SDK-lag, unrelated to this change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: SKILL.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -411,6 +411,8 @@ Rules and footnotes:
411
411
-**Deploy-time validation.** Missing table or column at activation fails with `DEPLOY_INVALID_ROLE_GATE` (422) *before* flipping the live release.
412
412
-**Cache TTL.** Default 60s, max 600s. A demoted user keeps the cached role until expiry — for instant revocation, set `cacheTtl: 0` (fresh lookup per request).
413
413
-**Gate applies to both** routed (`/your/route`) and direct (`POST /functions/v1/:name` with API key) invocation. Direct invocation still requires the API key at the edge; the gate runs after API-key auth, against the user JWT.
414
+
-**Reading the role (`@run402/functions` 3.4.0+).**`await auth.requireRole("operator")` returns `{ user, role }`; it throws a distinct `RoleGateNotConfiguredError` (500) when no `requireRole` gate is declared (vs `InsufficientRoleError` 403 for a real mismatch). For multi-role gates, `await auth.role()` returns the resolved role (or `null`, never throws) so you branch instead of re-asserting.
415
+
-**Scaffold + first-operator bootstrap.**`run402 auth scaffold-roles --roles operator` emits the `app_roles` migration, the `requireRole` snippet, and a service-role `INSERT` for the FIRST operator — the table starts empty, so the first grant bypasses RLS with the service key. The gate keys on the tenant user id (JWT `sub`), not a wallet.
`Put 'gate' on the function's requireRole deploy-spec field, then call auth.requireRole('${firstRole}') in the function — or auth.role() to branch when 'allowed' has multiple roles.`,
724
+
"requireRole(x) requires x in 'allowed'; for multi-role gates read auth.role() and branch instead of re-asserting.",
725
+
"cacheTtl is the role-lookup cache in seconds; set it to 0 for instant revocation (fresh DB read per request).",
726
+
"The gate keys on the tenant USER id (internal.users.id / JWT 'sub'), NOT a wallet address.",
727
+
`The gate accepts any table/columns — '${table}'(${userCol},${roleCol}) is the blessed default; point requireRole at your own table if you already have one.`,
Copy file name to clipboardExpand all lines: cli/llms-cli.txt
+5Lines changed: 5 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -639,6 +639,10 @@ Validation rules:
639
639
- Empty `allowed`. Rejected with `INVALID_SPEC` — set the role values explicitly.
640
640
- Deploy-time validation. Missing table or column at activation fails with `DEPLOY_INVALID_ROLE_GATE` (HTTP 422) *before* flipping the live release. `run402 deploy apply` surfaces the structured envelope on stderr.
641
641
642
+
Reading the role in-function (`@run402/functions` 3.4.0+). `await auth.requireRole("operator")` returns `{ user, role }` or throws — and throws a *distinct* `RoleGateNotConfiguredError` (server-class 500) when the function declares no `requireRole` gate, vs `InsufficientRoleError` (403) for a real role mismatch. When a gate allows more than one role, read `await auth.role()` (returns the resolved role or `null`, never throws) and branch instead of re-asserting — `requireRole(x)` requires `x` to be one of `allowed`.
643
+
644
+
Scaffolding + first-operator bootstrap. `run402 auth scaffold-roles --roles operator` emits the conventional `app_roles(user_id uuid, role text)` migration, the matching `requireRole` snippet, and a service-role `INSERT` to grant the FIRST role — the table starts empty, so the first grant must bypass RLS with the service key. The gate keys on the tenant user id (`internal.users.id` / JWT `sub`), NOT a wallet. The conventional table is a default — `requireRole` accepts any `(table, idColumn, roleColumn)`.
645
+
642
646
The gate applies to both routed (`/your/route`) and direct (`POST /functions/v1/:name` with API key plus user JWT) invocation. Direct invocation still requires the API key at the edge; the gate runs after API-key auth, against the user JWT.
643
647
644
648
Binary files (images, fonts, PDFs): Set `"encoding": "base64"` and provide base64-encoded data. MIME types are auto-detected from the file extension (`.png` → `image/png`, `.woff2` → `font/woff2`, etc.). Text files use `"encoding": "utf-8"` (the default — can be omitted).
@@ -1301,6 +1305,7 @@ Manage project user authentication: magic links, trusted invites, passwords, pas
Magic link flow: request → user clicks email link → frontend extracts token → verify → authenticated. Token expires in 15 minutes, single-use. Rate limited: 5 per email/hour, plus per-project limits by tier.
"Generate a role-table migration + requireRole gate snippet + first-operator bootstrap for Run402 function role gates. Offline and deterministic (no project or network). Inputs: table, user_col, role_col, roles[], cache_ttl.",
1153
+
scaffoldRolesSchema,
1154
+
async(args)=>handleScaffoldRoles(args),
1155
+
);
1156
+
1149
1157
server.tool(
1150
1158
"passkey_register_options",
1151
1159
"Create WebAuthn passkey registration options for the authenticated user.",
"**1. Migration** (apply once, or via your deploy spec's database migrations):",
76
+
"```sql",
77
+
migration,
78
+
"```",
79
+
"",
80
+
"**2. `requireRole` gate** — put this on the function in your deploy spec (`spec.functions[].requireRole`):",
81
+
"```json",
82
+
JSON.stringify(gate,null,2),
83
+
"```",
84
+
"",
85
+
`**3. In the function:** \`await auth.requireRole(${JSON.stringify(firstRole)})\` — or \`await auth.role()\` to branch when \`allowed\` has multiple roles.`,
86
+
"",
87
+
"**4. First-operator bootstrap** (the table starts empty — grant the first role once with the service key):",
88
+
"```sql",
89
+
bootstrap,
90
+
"```",
91
+
"",
92
+
"Notes:",
93
+
"- `requireRole(x)` requires `x` to be in `allowed`; for multi-role gates read `auth.role()` and branch instead of re-asserting.",
94
+
"- `cacheTtl` is the role-lookup cache in seconds; set it to `0` for instant revocation (fresh DB read per request).",
95
+
"- The gate keys on the tenant **user id** (`internal.users.id` / JWT `sub`), NOT a wallet address.",
96
+
`- The gate accepts any table/columns — \`${table}(${userCol}, ${roleCol})\` is the blessed default; point \`requireRole\` at your own table if you already have one.`,
0 commit comments