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 +4 -4
- package/package.json +1 -1
- package/template/AGENTS.md +9 -0
- package/template/src/operations.ts +20 -4
- package/template/src/provision.ts +8 -0
- package/template/src/worker.ts +16 -1
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.
|
|
40
|
-
const ENGINE_WORKORDER = '^0.11.
|
|
41
|
-
const ENGINE_INVOICING = '^0.
|
|
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.
|
|
43
|
+
const DEV_ISSUER = '^0.1.25';
|
|
44
44
|
|
|
45
45
|
const DOCS = 'https://substrat.net';
|
|
46
46
|
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -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
|
|
23
|
-
* is listed because a vertical operation may be gated by one — this
|
|
24
|
-
* vocabulary a `permission` may draw on, not a second declaration of who
|
|
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
|
});
|
package/template/src/worker.ts
CHANGED
|
@@ -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.
|