@mesh-tech/mesh-cli 0.22.1 → 0.22.2

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 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 export or SpiceDBSchema) \u2014 the Hub's create-key and role catalog 409 METADATA_UNPUBLISHED"
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: 'publish ops-hub metadata and end index.ts with new mesh.auth.AppAuthzPointer("authz", { env, mode, opsHubMetadataRef })',
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: {
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "2c9e53366",
3
- "builtAt": "2026-09-14T14:19:35.432Z"
2
+ "commit": "f521bf94d",
3
+ "builtAt": "2026-09-14T16:47:16.010Z"
4
4
  }
@@ -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: 'publish ops-hub metadata and end index.ts with new mesh.auth.AppAuthzPointer("authz", { env, mode, opsHubMetadataRef })',
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 export or SpiceDBSchema) — the Hub's create-key and role catalog 409 METADATA_UNPUBLISHED");
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.1",
3
+ "version": "0.22.2",
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.8.0",
45
+ "@mesh-tech/tsconfig": "2.9.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 `Export` `compileOpsHubMetadata(schema)` and pass
15
- `opsHubMetadataRef` to `AppAuthzPointer`, or the Hub create-key wizard
16
- 409s (`METADATA_UNPUBLISHED`).
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` exports `compileOpsHubMetadata(schema)`
37
- at the conventional path and threads `opsHubMetadataRef` into
38
- `AppAuthzPointer`. Without it the Hub's create-key wizard hard-409s
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, ops-hub metadata,
60
- `AppAuthzPointer` mode `role-gating`)
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 project.
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. ops-hub metadata — the compiled schema blob (coarseRoles + mode).
18
- * 6. AppAuthzPointer — mode "role-gating"; guard fails deploy if SpiceDB.
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 own project via zitadelAppProjectId. ---
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. Ops-hub metadata — MANDATORY for role-gating apps. ---
88
- // The Hub create-key path validates requested roles against the PUBLISHED
89
- // coarseRoles and hard-409s (METADATA_UNPUBLISHED) without it. Role-gating apps
90
- // have no SpiceDBSchema component to export it, so publish the compiled blob
91
- // directly at the conventional path the Hub assembler sweeps.
92
- const opsHubMetadataRef = `/mesh-platform/${tenant}/${platform.env}/apps/${appName}/stacks/${stack}/authz/ops-hub-metadata`;
93
- new mesh.exports.Export<OpsHubMetadata>("ops-hub-metadata", {
94
- path: opsHubMetadataRef,
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", { env, mode: "role-gating", opsHubMetadataRef });
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
- Both were discovered the hard way and each costs a redeploy:
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` does
39
- `mesh.exports.Export(compileOpsHubMetadata(schema))` at
40
- `…/apps/{{name}}/stacks/{stack}/authz/ops-hub-metadata` and passes that
41
- `opsHubMetadataRef` to `AppAuthzPointer`. A role-gating app has no
42
- `SpiceDBSchema` component to publish this, and the Hub create-key wizard
43
- hard-409s (`METADATA_UNPUBLISHED`) without it — it validates requested roles
44
- against the app's PUBLISHED `coarseRoles`.
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