@mesh-tech/mesh-cli 0.22.1 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mesh.js +2 -2
- package/dist/build-info.json +2 -2
- package/dist/src/commands/app-check.js +2 -2
- package/package.json +2 -2
- package/templates/api-role-gating/AGENTS.md.hbs +4 -3
- package/templates/api-role-gating/README.md.hbs +6 -5
- package/templates/api-role-gating/index.ts.hbs +23 -19
- package/templates/api-role-gating/skills/develop/SKILL.md.hbs +13 -8
package/dist/bin/mesh.js
CHANGED
|
@@ -11084,7 +11084,7 @@ function scanApp(root, appRel) {
|
|
|
11084
11084
|
hit(
|
|
11085
11085
|
"METADATA_UNPUBLISHED",
|
|
11086
11086
|
rel(root, pointer.file),
|
|
11087
|
-
"AppAuthzPointer present but no compiled ops-hub metadata (compileOpsHubMetadata
|
|
11087
|
+
"AppAuthzPointer present but no compiled ops-hub metadata (opsHubMetadata: compileOpsHubMetadata(schemaDef) on the pointer, or a SpiceDBSchema) \u2014 the Hub's create-key and role catalog 409 METADATA_UNPUBLISHED"
|
|
11088
11088
|
);
|
|
11089
11089
|
}
|
|
11090
11090
|
const anti = (code, re, why) => {
|
|
@@ -11261,7 +11261,7 @@ var init_app_check = __esm({
|
|
|
11261
11261
|
gate: "H",
|
|
11262
11262
|
title: "Access surfaced through the Hub",
|
|
11263
11263
|
severity: "block",
|
|
11264
|
-
remediation: '
|
|
11264
|
+
remediation: 'end index.ts with new mesh.auth.AppAuthzPointer("authz", { env, mode, opsHubMetadata: compileOpsHubMetadata(schemaDef) }) (role-gating) or { env, mode, opsHubMetadataRef: schema.opsHubMetadataRef } (policy-engine)',
|
|
11265
11265
|
scope: "app"
|
|
11266
11266
|
},
|
|
11267
11267
|
METADATA_UNPUBLISHED: {
|
package/dist/build-info.json
CHANGED
|
@@ -51,7 +51,7 @@ export const APP_CHECK_RULES = {
|
|
|
51
51
|
gate: "H",
|
|
52
52
|
title: "Access surfaced through the Hub",
|
|
53
53
|
severity: "block",
|
|
54
|
-
remediation: '
|
|
54
|
+
remediation: 'end index.ts with new mesh.auth.AppAuthzPointer("authz", { env, mode, opsHubMetadata: compileOpsHubMetadata(schemaDef) }) (role-gating) or { env, mode, opsHubMetadataRef: schema.opsHubMetadataRef } (policy-engine)',
|
|
55
55
|
scope: "app",
|
|
56
56
|
},
|
|
57
57
|
METADATA_UNPUBLISHED: {
|
|
@@ -570,7 +570,7 @@ export function scanApp(root, appRel) {
|
|
|
570
570
|
hit("NO_AUTHZ_POINTER", rel(root, at.file), `${appLabel} authenticates callers or declares roles but publishes no mesh.auth.AppAuthzPointer — the Hub's Access tab is blank for this app`);
|
|
571
571
|
}
|
|
572
572
|
else if (exposure && pointer && !metadata) {
|
|
573
|
-
hit("METADATA_UNPUBLISHED", rel(root, pointer.file), "AppAuthzPointer present but no compiled ops-hub metadata (compileOpsHubMetadata
|
|
573
|
+
hit("METADATA_UNPUBLISHED", rel(root, pointer.file), "AppAuthzPointer present but no compiled ops-hub metadata (opsHubMetadata: compileOpsHubMetadata(schemaDef) on the pointer, or a SpiceDBSchema) — the Hub's create-key and role catalog 409 METADATA_UNPUBLISHED");
|
|
574
574
|
}
|
|
575
575
|
const anti = (code, re, why) => {
|
|
576
576
|
const files = [...new Set(surface.filter((h) => re.test(h.text)).map((h) => h.file))].sort().slice(0, 3);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mesh-tech/mesh-cli",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.3",
|
|
4
4
|
"description": "CLI for Mesh platform development utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@mesh-tech/agent-contracts": "0.2.0",
|
|
45
|
-
"@mesh-tech/tsconfig": "2.
|
|
45
|
+
"@mesh-tech/tsconfig": "2.10.0",
|
|
46
46
|
"@mesh-tech/workflow-viz": "0.3.0",
|
|
47
47
|
"@tanstack/intent": "^0.3.6",
|
|
48
48
|
"@types/node": "^20.0.0",
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
- **Two non-negotiables for role-gating apps (already wired — keep them):**
|
|
12
12
|
1. `api/package.json` build MUST be `esm-bundle …`, not `tsc` — plain `tsc`
|
|
13
13
|
ships bare `@mesh-tech/*` imports the container can't resolve.
|
|
14
|
-
2. `index.ts` MUST `
|
|
15
|
-
`
|
|
16
|
-
|
|
14
|
+
2. `index.ts` MUST pass `opsHubMetadata: compileOpsHubMetadata(schema)` to
|
|
15
|
+
`AppAuthzPointer` — the pointer publishes the blob at the conventional
|
|
16
|
+
path itself; do NOT add a separate `mesh.exports.Export`. Without it the
|
|
17
|
+
Hub create-key wizard 409s (`METADATA_UNPUBLISHED`).
|
|
17
18
|
- Machine/API-key callers carry roles under the project-id-keyed Zitadel claim;
|
|
18
19
|
`authn.ts` passes `projectId` to `zitadelJwtScheme` so it reads the right key.
|
|
19
20
|
- Pattern skills for authn/authz ship inside those packages (TanStack Intent
|
|
@@ -33,9 +33,10 @@ mesh dev # wires the api to the local platform
|
|
|
33
33
|
`esm-bundle src/main.ts --outfile dist/main.js` — plain `tsc` leaves bare
|
|
34
34
|
`@mesh-tech/*` workspace imports unresolvable in the container
|
|
35
35
|
(`ERR_MODULE_NOT_FOUND` / CrashLoopBackOff).
|
|
36
|
-
2. **Publish ops-hub metadata.** `index.ts`
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
2. **Publish ops-hub metadata.** `index.ts` hands
|
|
37
|
+
`opsHubMetadata: compileOpsHubMetadata(schema)` to `AppAuthzPointer`, which
|
|
38
|
+
publishes the blob at the conventional path — no separate
|
|
39
|
+
`mesh.exports.Export`. Without it the Hub's create-key wizard hard-409s
|
|
39
40
|
(`METADATA_UNPUBLISHED`) — a role-gating app has no `SpiceDBSchema` component
|
|
40
41
|
to publish the blob for it.
|
|
41
42
|
|
|
@@ -56,8 +57,8 @@ at preview time).
|
|
|
56
57
|
|
|
57
58
|
## Layout
|
|
58
59
|
|
|
59
|
-
- `index.ts` — Pulumi program (identity + roles, Service,
|
|
60
|
-
|
|
60
|
+
- `index.ts` — Pulumi program (identity + roles, Service, `AppAuthzPointer`
|
|
61
|
+
mode `role-gating` publishing the ops-hub metadata)
|
|
61
62
|
- `api/src/main.ts` — Hono app: authn middleware + `m.requireRole(...)`
|
|
62
63
|
- `api/src/schema.ts` — the authz schema (single source of truth; `coarseRoles`)
|
|
63
64
|
- `api/src/authn.ts` — Zitadel JWT scheme (project-id-keyed roles claim)
|
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
* 2. ZitadelAppIdentity — this app's OWN project; roles = schema.coarseRoles
|
|
13
13
|
* (roleAssertion defaults ON so granted roles ride in
|
|
14
14
|
* tokens — essential; there is no SpiceDB fallback).
|
|
15
|
-
* 3. AppEnvironment — namespace + providers, bound to the app's
|
|
15
|
+
* 3. AppEnvironment — namespace + providers, bound to the app's OWN
|
|
16
|
+
* project via zitadelAppProjectId (the pointer's
|
|
17
|
+
* guard fails the deploy if it is the platform's).
|
|
16
18
|
* 4. api Service — Hono app gating routes by role, in-process.
|
|
17
|
-
* 5.
|
|
18
|
-
*
|
|
19
|
+
* 5. AppAuthzPointer — mode "role-gating"; publishes the compiled schema
|
|
20
|
+
* blob (coarseRoles + mode) as the ops-hub metadata
|
|
21
|
+
* at …/authz/ops-hub-metadata and points at it.
|
|
19
22
|
*
|
|
20
23
|
* Prerequisite: the target platform must have Zitadel enabled — ZitadelConnector
|
|
21
24
|
* eager-reads it at preview time.
|
|
@@ -26,7 +29,6 @@
|
|
|
26
29
|
import * as pulumi from "@pulumi/pulumi";
|
|
27
30
|
import { mesh } from "@mesh-tech/app-kit/infra";
|
|
28
31
|
import { compileOpsHubMetadata } from "@mesh-tech/authz";
|
|
29
|
-
import type { OpsHubMetadata } from "@mesh-tech/authz";
|
|
30
32
|
// The authz schema is the single source of truth, shared by the api (enforces
|
|
31
33
|
// it) and this Pulumi program (compiles it to ops-hub metadata + Zitadel roles).
|
|
32
34
|
import schemaDef from "./api/src/schema.js";
|
|
@@ -54,7 +56,10 @@ const identity = new mesh.auth.ZitadelAppIdentity("identity", {
|
|
|
54
56
|
},
|
|
55
57
|
});
|
|
56
58
|
|
|
57
|
-
// --- 3. Environment — bound to the app's
|
|
59
|
+
// --- 3. Environment — bound to the app's OWN project via zitadelAppProjectId. ---
|
|
60
|
+
// Without it env.zitadel.projectId silently falls back to the shared platform
|
|
61
|
+
// project, whose roles are not this app's — AppAuthzPointer below fails the
|
|
62
|
+
// deploy for a role-gating app in that state, so this line is load-bearing.
|
|
58
63
|
const env = new mesh.apps.AppEnvironment("env", {
|
|
59
64
|
tenant,
|
|
60
65
|
platform,
|
|
@@ -84,22 +89,21 @@ const api = new mesh.apps.Service("api", {
|
|
|
84
89
|
resources: { cpu: "100m", memory: "128Mi" },
|
|
85
90
|
});
|
|
86
91
|
|
|
87
|
-
// --- 5.
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
value: compileOpsHubMetadata(schemaDef),
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// --- 6. AppAuthzPointer — LAST. mode "role-gating" tells Ops Hub to render the
|
|
99
|
-
// role-gating surface; its guard fails the deploy if any SpiceDB is registered.
|
|
92
|
+
// --- 5. AppAuthzPointer — LAST. mode "role-gating" tells Ops Hub to render the
|
|
93
|
+
// role-gating surface; its guards fail the deploy if any SpiceDB is registered
|
|
94
|
+
// or if the env is bound to the platform project instead of this app's.
|
|
95
|
+
// `opsHubMetadata` is MANDATORY for role-gating apps: the Hub create-key path
|
|
96
|
+
// validates requested roles against the PUBLISHED coarseRoles and hard-409s
|
|
97
|
+
// (METADATA_UNPUBLISHED) without it. Role-gating apps have no SpiceDBSchema
|
|
98
|
+
// component to publish it, so the pointer publishes the compiled blob itself
|
|
99
|
+
// at the conventional …/authz/ops-hub-metadata path and references it.
|
|
100
100
|
// It publishes env.zitadel — the project bound above through zitadelAppProjectId;
|
|
101
101
|
// never pass a `zitadel:` override here (that is the Hub's own escape hatch). ---
|
|
102
|
-
new mesh.auth.AppAuthzPointer("authz", {
|
|
102
|
+
new mesh.auth.AppAuthzPointer("authz", {
|
|
103
|
+
env,
|
|
104
|
+
mode: "role-gating",
|
|
105
|
+
opsHubMetadata: compileOpsHubMetadata(schemaDef),
|
|
106
|
+
});
|
|
103
107
|
|
|
104
108
|
export const app = env.register({
|
|
105
109
|
description: "{{titleCase name}} — role-gating API (roles enforced in-process from the Zitadel JWT)",
|
|
@@ -28,20 +28,25 @@ A token without the role → 403; no/invalid token on a gated route → 401.
|
|
|
28
28
|
|
|
29
29
|
## Two role-gating gotchas (already wired — do NOT regress)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Each costs a redeploy to rediscover:
|
|
32
32
|
|
|
33
33
|
1. **`esm-bundle` build, not `tsc`.** `api/package.json`:
|
|
34
34
|
`"build": "esm-bundle src/main.ts --outfile dist/main.js"` with the
|
|
35
35
|
`@mesh-tech/esm-bundle` devDep. Plain `tsc` leaves bare `@mesh-tech/*`
|
|
36
36
|
workspace imports unresolvable in the Service container
|
|
37
37
|
(`ERR_MODULE_NOT_FOUND` / CrashLoopBackOff).
|
|
38
|
-
2. **Publish ops-hub metadata.** `index.ts`
|
|
39
|
-
`
|
|
40
|
-
`…/apps/{{name}}/stacks/{stack}/authz/ops-hub-metadata` and
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
2. **Publish ops-hub metadata.** `index.ts` passes
|
|
39
|
+
`opsHubMetadata: compileOpsHubMetadata(schema)` to `AppAuthzPointer`, which
|
|
40
|
+
publishes it at `…/apps/{{name}}/stacks/{stack}/authz/ops-hub-metadata` and
|
|
41
|
+
references it. A role-gating app has no `SpiceDBSchema` component to publish
|
|
42
|
+
this, and the Hub create-key wizard hard-409s (`METADATA_UNPUBLISHED`)
|
|
43
|
+
without it — it validates requested roles against the app's PUBLISHED
|
|
44
|
+
`coarseRoles`.
|
|
45
|
+
3. **Bind the env to this app's OWN Zitadel project.** `ZitadelAppIdentity` is
|
|
46
|
+
constructed BEFORE `AppEnvironment`, which takes
|
|
47
|
+
`zitadelAppProjectId: identity.projectId`. Without it the env silently falls
|
|
48
|
+
back to the shared platform project (no roles of this app live there) and
|
|
49
|
+
`AppAuthzPointer` fails the deploy naming both projects and this fix.
|
|
45
50
|
|
|
46
51
|
## Never add SpiceDB
|
|
47
52
|
|