create-substrat 0.9.3 → 0.9.5

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/index.js CHANGED
@@ -36,11 +36,11 @@ const TEMPLATE = join(HERE, 'template');
36
36
  // The runtime packages release together off one version line (the changesets `fixed`
37
37
  // group), so one constant is right for all of them. Engines do NOT share a line —
38
38
  // each versions on its own, so one pin per engine, deliberately.
39
- const SUBSTRAT = '^0.105.0';
40
- const ENGINE_WORKORDER = '^0.11.4';
41
- const ENGINE_INVOICING = '^0.9.21';
39
+ const SUBSTRAT = '^0.110.0';
40
+ const ENGINE_WORKORDER = '^0.11.9';
41
+ const ENGINE_INVOICING = '^0.10.0';
42
42
  const BOUNDARY_LINT = '^0.4.2';
43
- const DEV_ISSUER = '^0.1.20';
43
+ const DEV_ISSUER = '^0.1.25';
44
44
 
45
45
  const DOCS = 'https://substrat.net';
46
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-substrat",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Scaffold a Substrat vertical — `npm create substrat <dir>`.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -307,6 +307,15 @@ vertical done, boot the server and drive the real flow over HTTP as two personas
307
307
  should succeed and one who should be denied — and confirm the denial arrives as a denial
308
308
  (not a generic error).
309
309
 
310
+ `.claude/launch.json` is what starts those servers for an agent with a browser pane, and it
311
+ declares a **port** — never a path or a query. So the pane opens each server's bare origin,
312
+ and no entry can deep-link a page underneath it: reaching one is a navigation you perform
313
+ once the preview is up, not something configuration expresses. Do that without being asked.
314
+ The page worth opening first on a vertical that serves the Scalar API reference is
315
+ **`/api/docs`** — every operation, rendered from that vertical's own `/openapi.json`, on the
316
+ vertical's own origin, so a try-it request carries the session cookie you already have and
317
+ a 403 in the playground is the permission system working rather than a broken page.
318
+
310
319
  ## When it breaks — symptom → fix
311
320
 
312
321
  Six failures that have each cost someone a day. What makes them expensive is that none of
@@ -19,18 +19,34 @@ import { bikeShopEntities } from './entities.js';
19
19
  * The permission keys an operation here may name.
20
20
  *
21
21
  * Two of them are this vertical's own (they mirror `SHOP_PERM` in
22
- * `src/manifest.ts`); the other four are the WORKORDER ENGINE's. An engine key
23
- * is listed because a vertical operation may be gated by one — this is the
24
- * vocabulary a `permission` may draw on, not a second declaration of who owns
25
- * the key. The engine still declares them.
22
+ * `src/manifest.ts`); the rest belong to the ENGINES this vertical composes. An
23
+ * engine key is listed because a vertical operation may be gated by one — this
24
+ * is the vocabulary a `permission` may draw on, not a second declaration of who
25
+ * owns the key. The engine still declares them.
26
+ *
27
+ * One array, two readers, and that is what makes it checked (#1208).
28
+ * `defineOperations` takes it below as the union a mistyped `permission:` fails
29
+ * against; `definePermissions` in `src/provision.ts` takes the SAME array as
30
+ * `keys` and throws at module load if it and `MODULES` disagree in either
31
+ * direction. So every key a registered module declares is here, including the
32
+ * ones no shop operation checks today — `MODULES` registers both engines, so a
33
+ * scope declares them, and an operation gated on one had no way to say so while
34
+ * the list held only the subset the shop happened to check.
26
35
  */
27
36
  export const SHOP_PERMISSIONS = [
37
+ // The shop's own — `SHOP_PERM` in src/manifest.ts.
28
38
  'customer:manage',
29
39
  'bike:manage',
40
+ // @substrat-run/engine-workorder
30
41
  'workorder:create',
31
42
  'workorder:read',
43
+ 'workorder:assign',
44
+ 'workorder:report',
32
45
  'workorder:complete',
33
46
  'workorder:close',
47
+ // @substrat-run/engine-invoicing
48
+ 'invoicing:read',
49
+ 'invoicing:export',
34
50
  ] as const;
35
51
 
36
52
  /**
@@ -7,6 +7,7 @@ import { workorderModule, PERM as WO } from '@substrat-run/engine-workorder';
7
7
  import { invoicingModule, INVOICING_PERM as INV } from '@substrat-run/engine-invoicing';
8
8
  import { bikeShopModule } from './module.js';
9
9
  import { SHOP_PERM } from './manifest.js';
10
+ import { SHOP_PERMISSIONS } from './operations.js';
10
11
 
11
12
  // The manifest's config surface rides the same import `substrat push` already makes for
12
13
  // `permissions` (#1206): this export is what the push uploads, so `src/manifest.ts` is the
@@ -73,9 +74,16 @@ export const ENTITY_GRANTS: { entityType: string; permissions: PermissionKey[] }
73
74
  * The single typed source for this vertical's permission surface — what the
74
75
  * permission checkpoint and `substrat push` read (via package.json
75
76
  * `substrat.permissions`).
77
+ *
78
+ * `keys` is `SHOP_PERMISSIONS` — the array `src/operations.ts` already hands
79
+ * `defineOperations` as the union a mistyped `permission:` fails against — which
80
+ * is what makes that restatement checked (#1208): `definePermissions` throws at
81
+ * module load if it and `MODULES` disagree in either direction. Keep handing
82
+ * both readers the SAME array; a second copy is the thing this removes.
76
83
  */
77
84
  export const permissions = definePermissions({
78
85
  modules: MODULES,
79
86
  roles: ROLES,
80
87
  entityGrants: ENTITY_GRANTS,
88
+ keys: SHOP_PERMISSIONS,
81
89
  });
@@ -47,7 +47,7 @@ import {
47
47
  SCOPE_SWEEPER_NAME,
48
48
  type ScopeSweeperDo,
49
49
  } from '@substrat-run/adapter-cloudflare';
50
- import { readRoutedNode, RouterAssertionError, type ScopeStub } from '@substrat-run/kernel';
50
+ import { readRoutedNode, RouterAssertionError, type ScopeStub, invocationLog } from '@substrat-run/kernel';
51
51
  import { mountPlatformSurface } from '@substrat-run/vertical-host';
52
52
  import { MODULES, OWNER_ROLE_KEY, ROLES } from './provision.js';
53
53
  import { SHOP_ENV } from './manifest.js';
@@ -239,6 +239,21 @@ async function unauthorizedReason(env: Env, node: Node): Promise<string> {
239
239
 
240
240
  const app = new Hono<{ Bindings: Env }>();
241
241
 
242
+ // FIRST, before any route: Hono composes handlers in registration order and stops at the
243
+ // one that answers, so a route registered above this line would never be logged. The line
244
+ // it writes is what attributes this vertical's invocations to the tenant they served —
245
+ // see `invocationLog`'s header for why the router cannot supply that from its side.
246
+ app.use(
247
+ '*',
248
+ invocationLog<Env>({
249
+ // The same two answers this worker gives `readRoutedNode` in `nodeFor`: the line is
250
+ // written from a VERIFIED assertion, never from the header, or anyone who could reach
251
+ // this script directly could file their request under a tenant of their choosing.
252
+ routerSecret: (env) => env.ROUTER_SECRET,
253
+ allowUnsigned: (env) => env.ALLOW_DEV_NODE === 'true',
254
+ }),
255
+ );
256
+
242
257
  // Who am I, and what instance am I on — resolves the caller without invoking
243
258
  // anything. Auth-shaped and host-specific, so it stays OUT of the shared table;
244
259
  // `server.ts` answers the same question from its own login.