@astrale-os/adapter-cloudflare 0.2.1 → 0.3.0

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.
Files changed (64) hide show
  1. package/dist/build.d.ts +2 -1
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/build.js +1 -1
  4. package/dist/build.js.map +1 -1
  5. package/dist/client.d.ts +14 -13
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +25 -18
  8. package/dist/client.js.map +1 -1
  9. package/dist/cloudflare.d.ts +4 -1
  10. package/dist/cloudflare.d.ts.map +1 -1
  11. package/dist/cloudflare.js +40 -18
  12. package/dist/cloudflare.js.map +1 -1
  13. package/dist/codegen/worker.d.ts +3 -3
  14. package/dist/codegen/worker.d.ts.map +1 -1
  15. package/dist/codegen/worker.js +19 -8
  16. package/dist/codegen/worker.js.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js +2 -2
  19. package/dist/params.d.ts +3 -0
  20. package/dist/params.d.ts.map +1 -1
  21. package/package.json +2 -2
  22. package/src/build.ts +2 -1
  23. package/src/client.ts +43 -18
  24. package/src/cloudflare.ts +41 -14
  25. package/src/codegen/worker.ts +19 -8
  26. package/src/index.ts +2 -2
  27. package/src/params.ts +4 -0
  28. package/template/CLAUDE.md +24 -0
  29. package/template/README.md +24 -15
  30. package/template/astrale.config.ts +4 -4
  31. package/template/client/README.md +9 -9
  32. package/template/client/__tests__/app.test.tsx +58 -19
  33. package/template/client/__tests__/harness.ts +16 -0
  34. package/template/client/__tests__/kernel.test.ts +23 -3
  35. package/template/client/src/shell/use-async.ts +4 -1
  36. package/template/client/src/status/components/StatusCard.tsx +115 -5
  37. package/template/client/src/status/hooks/useCheckable.query.ts +48 -40
  38. package/template/client/src/status/index.ts +2 -2
  39. package/template/client/src/status/status.api.ts +18 -1
  40. package/template/client/src/status/status.mappers.ts +89 -6
  41. package/template/client/src/status/status.types.ts +17 -2
  42. package/template/client/src/styles.css +235 -14
  43. package/template/client/src/views/status.tsx +1 -1
  44. package/template/core/monitor/index.ts +2 -2
  45. package/template/core/monitor/node.ts +12 -6
  46. package/template/domain.ts +6 -4
  47. package/template/functions/index.ts +31 -7
  48. package/template/package.json +2 -2
  49. package/template/pnpm-lock.yaml +2780 -0
  50. package/template/runtime/index.ts +8 -17
  51. package/template/runtime/monitoring/index.ts +8 -0
  52. package/template/runtime/{monitor → monitoring/monitor}/check.ts +3 -3
  53. package/template/runtime/{monitor → monitoring/monitor}/index.ts +3 -2
  54. package/template/runtime/{monitor → monitoring/monitor}/seed.ts +19 -10
  55. package/template/runtime/{monitor → monitoring/monitor}/watch.ts +5 -5
  56. package/template/runtime/{status-page → monitoring/page}/add.ts +2 -2
  57. package/template/runtime/{status-page → monitoring/page}/check.ts +2 -2
  58. package/template/runtime/{status-page → monitoring/page}/create.ts +3 -3
  59. package/template/runtime/monitoring/page/index.ts +9 -0
  60. package/template/schema/monitor.ts +6 -8
  61. package/template/views/index.ts +1 -1
  62. package/template/.agents/skills/astrale-cli/SKILL.md +0 -458
  63. package/template/.agents/skills/astrale-domain/SKILL.md +0 -372
  64. package/template/runtime/status-page/index.ts +0 -8
@@ -1,372 +0,0 @@
1
- ---
2
- name: Astrale Domain
3
- description: Create, develop, deploy, and iterate an Astrale domain end-to-end — schema modeling (classes, interfaces, edges, methods), handler implementation, calling the kernel and other domains from handlers, integrating external APIs (the core use case — DI, secrets, idempotency, sagas), views, testing, and the deploy/install loop with the cloudflare or astrale adapter. Use whenever creating a domain, adding classes/methods/views, structuring external API calls, or debugging install/dispatch/permission errors. Pairs with the astrale-cli skill (CLI ops) — together they cover working an Astrale instance autonomously. `npx create-astrale-domain <name>` scaffolds a new domain project.
4
- when_to_use: when asked to create or edit a domain.
5
- ---
6
-
7
- # Astrale Domain — the authoring bible
8
-
9
- An Astrale **domain** is a typed contract installed into a kernel's graph plus a
10
- **worker you own** that executes it. The kernel stores the schema (classes,
11
- edges, function contracts), enforces permissions, and routes calls; every
12
- method body runs on YOUR worker (Cloudflare worker or Astrale-managed service).
13
- The graph is the database AND the bus: domains read/write nodes+edges through
14
- the kernel and call each other's functions through it.
15
-
16
- **Mental model:** schema = what exists and what's callable · graph = state ·
17
- worker = behavior · kernel = router + authorizer. You never run a database or
18
- an RPC layer; you declare a contract and implement handlers.
19
-
20
- ## 0 · Start
21
-
22
- ```bash
23
- npx -y create-astrale-domain@latest my-domain --yes --instance <slug> # scaffold
24
- # managed `astrale` adapter is the DEFAULT; `--instance` stamps the
25
- # target instance into prod (`--adapter cloudflare` = your own CF account)
26
- cd my-domain && pnpm install
27
- pnpm dev # local wrangler dev (prints a URL; install it on an instance to test)
28
- # port 8787 taken? dev AUTO-PICKS a free one and prints it —
29
- # always trust the printed URL, and `curl <url>/meta` must name
30
- # YOUR domain (an orphan wrangler answers on stolen ports).
31
- pnpm dev --port 8899 # pin a port explicitly (disables auto-pick)
32
- pnpm dev --host https://my-box.preview.dev # dev behind a tunnel/sandbox preview:
33
- # binds 0.0.0.0 + pins WORKER_URL so iss doesn't drift
34
- pnpm prod # deploy + (astrale adapter) auto-install on your instance
35
- ```
36
-
37
- Project anatomy (the scaffold is the reference — read its comments):
38
-
39
- ```
40
- domain.ts # THE manifest — wires it ALL: defineDomain({ schema, methods,
41
- # deps, views, functions, client }) + origin/postInstall.
42
- # Modules are imported & passed EXPLICITLY (no folder magic);
43
- # a renamed module is a compile error here, not a missing route.
44
- astrale.config.ts # binds the domain to its deploy adapter (deploy(domain, …)) — node-only
45
- schema/ # classes/interfaces/edges — the contract (zod props, fn signatures)
46
- # + index.ts exports D = compileDomain(schema) (resolved paths/keys)
47
- core/<context>/ # pure, transport-agnostic logic per bounded context (e.g. core/monitor: keys/health/node)
48
- integrations/ # external-API ports + adapters + a lazy registry (see §4) — what deps is built from
49
- runtime/index.ts # composition root: the methods map; each execute resolves deps → calls core logic
50
- functions/ # standalone remote functions (webhook-shaped endpoints)
51
- views/ # iframe-mountable UI declarations (defineView)
52
- client/ # the SPA served under /ui (vite)
53
- deps.ts # env → typed Deps container (the seam defineDomain({ deps }) mounts)
54
- env.ts # typed worker env — config + secrets arrive here, mapped by deps.ts → ctx.deps
55
- ```
56
-
57
- Iteration loop: edit → `pnpm prod` → call it. Managed redeploys keep the same
58
- service URL and reinstall the contract; schema changes are merge/reconcile
59
- (removed entries can leave old nodes behind — see the astrale-live-domain-edit
60
- skill for graph-level cleanup).
61
-
62
- ## 1 · Schema modeling
63
-
64
- ```ts
65
- import { KernelSchema, defineSchema, edgeClass, nodeClass, nodeInterface } from '@astrale-os/kernel-core'
66
- import { fn } from '@astrale-os/kernel-dsl'
67
- import { z } from 'zod'
68
-
69
- export const ContactOps = nodeInterface({ // interface = shared contract
70
- methods: { createContact: fn({ static: true, params: {...}, returns: ... }) },
71
- })
72
- export const Contact = nodeClass({
73
- implements: [ContactOps, KernelSchema.interfaces.Container], // Container → can hold children
74
- props: { email: z.string(), company: z.string().optional() },
75
- methods: { assign: fn({ params: { project: z.string(), role: z.string() }, returns: ... }) },
76
- })
77
- export const works_on = edgeClass( // edges are CLASSES with PROPS
78
- { as: 'contact', types: [Contact] },
79
- { as: 'project', types: [Project] },
80
- { props: { role: z.string() } },
81
- )
82
- export const schema = defineSchema('my-domain.example.dev', {
83
- interfaces: { ContactOps },
84
- classes: { Contact, Project, works_on }, // EDGE classes register under `classes` too
85
- imports: [KernelSchema],
86
- })
87
- ```
88
-
89
- Conventions and rules:
90
- - Naming: classes/interfaces `PascalCase`, methods `camelCase`, **edges `snake_case`**.
91
- - `static: true` = called on the class (`/origin/class.X/method`); otherwise
92
- instance-dispatched (`/path/to/node::method`, handler gets `self`). Statics
93
- work on classes AND interfaces, wired through the same `method()` /
94
- `classMethods()` helpers (the scaffold's `seed` is a class-hosted static).
95
- - Model relations as **edges with props** (not foreign-key strings): create via
96
- `::link {edgeClass, target, props}`, walk via `::getLinks`. Prefer FLAT nodes
97
- + edges for peer resources; use Folders (Container) only for list-able
98
- hierarchies (`::listChildren` works on Folders).
99
- - Props are zod; they land in the graph under QUALIFIED keys
100
- (`origin:class.X.property.y`). Never hand-write key strings — derive them
101
- from the compiled schema: `D.Contact.email.key`, `K.Named.name.key`.
102
- - Known sharp edges: `::update` currently drops `z.enum()` props silently
103
- (create is fine — track status as plain string if you must update it); edge
104
- prop accessors exist at runtime but not in types yet (cast).
105
-
106
- ## 2 · Handlers
107
-
108
- Separate LOGIC from WIRING. Logic = plain async functions in `core/` taking the
109
- kernel client + ports + params (testable with fakes). Wiring = `method()` /
110
- `classMethods()` / `interfaceMethods()` in `runtime/index.ts` (the composition
111
- root — the only place request context + `deps` meet the `core/` logic):
112
-
113
- ```ts
114
- const kickoff = method(schema, 'Project', 'kickoff', {
115
- authorize: async () => undefined, // see §6 for real authorization
116
- execute: ({ kernel, self, params, deps }) => {
117
- if (!kernel) throw new Error('kickoff requires a kernel credential')
118
- return kickoffLogic(kernel, createWeatherClient(deps), self.path.raw, params)
119
- },
120
- })
121
- ```
122
-
123
- Handler context: `kernel` (callback client bound to the composed credential —
124
- caller's delegated authority ∪ this function's own), `self` (instance methods),
125
- `params` (zod-validated), `deps` (your typed `Deps` from `deps.ts`; raw `Env` if
126
- you omit the mapper), `auth` (principal, verified claims). Resolve ports from
127
- `deps` per request (`deps.prober()` — the registry builds + caches them per
128
- isolate) — NEVER construct external clients at module load (workers must be
129
- import-side-effect-free).
130
-
131
- ## 3 · Talking to the kernel (and other domains)
132
-
133
- Everything is `kernel.call(path, params)`. Addressing forms:
134
-
135
- | Form | Example | Use |
136
- |---|---|---|
137
- | tree path + `::method` | `/projects/apollo::get` | instance dispatch on a node |
138
- | static slash form | `/origin/class.X/method` | static class/interface methods |
139
- | colon MethodPath | `/:origin:class.X:method` | canonical form; REQUIRED in postInstall |
140
- | `@<uuid>::method` | `@4548…::get` | by graph id (NOT slugs/paths) |
141
-
142
- Core ops from handlers (all proven patterns):
143
-
144
- ```ts
145
- await kernel.call(K.Node.createNode.path.method.raw, { class: D.Contact.path.class.raw, path, props })
146
- await kernel.call(`${path}::update`, { props: { [D.Project.title.key]: 'New' } })
147
- // Edge props use QUALIFIED keys; the edge-prop TYPE accessor is missing today — use this cast:
148
- const ROLE_KEY = (D.works_on as unknown as { role: { key: string } }).role.key
149
- await kernel.call(`${path}::link`, { edgeClass: D.works_on.path.class.raw, target, props: { [ROLE_KEY]: role } })
150
- const links = await kernel.call(`${path}::getLinks`, {}) // filter by edge class
151
- await kernel.call('/other.domain/class.Echo/echo', { message }) // ANOTHER domain's function
152
- ```
153
-
154
- - **Cross-function calls work** (same- or cross-domain): the kernel redirects
155
- to the target worker and your worker mints a next-hop delegation as ITSELF —
156
- the receiver sees your function as the caller, with the original caller's
157
- authority threaded through the grant chain. Design service-to-service flows
158
- on this; no direct HTTP between workers.
159
- - Upsert idiom: try `createNode`, on path-conflict fall back to `::update`.
160
- - A missing parent yields `Permission denied: EDIT on /<parent>` — read that
161
- error as "does the parent exist?" first. Seed required folders in postInstall.
162
-
163
- ## 4 · External APIs — the core pattern
164
-
165
- Almost every real domain wraps an external API (payment, calendar, LLM
166
- gateway, cloud provider). The shape (see admin/domain for the full-scale
167
- example — Scaleway/WorkOS/KV):
168
-
169
- 1. **Port** — a narrow interface in `integrations/<feature>/port.ts` declaring
170
- only what the logic needs (`WeatherClient { forecast(city) }`, the scaffold's
171
- `Prober { probe(url) }`). `core/` logic depends on the port, never
172
- on fetch/SDKs/env.
173
- 2. **Adapter(s) + registry** — `createXClient(config)` in
174
- `integrations/<feature>/` (one per backend), plus a `registry.ts` that reads
175
- config + secrets from `env`, validates LOUDLY (`if (!env.X_API_KEY) throw`),
176
- sets base URLs (overridable so tests point at a stub), timeouts
177
- (`AbortSignal.timeout`), and maps upstream failures to errors with the
178
- upstream detail in `cause`. The registry builds the chosen adapter LAZILY +
179
- caches it per isolate (a worker never validates an unused backend's env).
180
- 3. **Wiring** — `deps.ts` mounts the registry (`defineDomain({ deps })`); the
181
- `execute` hook resolves the PORT from `deps` (`deps.prober()`) per
182
- request and passes it to the `core/` logic.
183
-
184
- Secrets & config:
185
- - Declare every var as a typed field on `Env` in `env.ts`. Secrets ship via
186
- the adapter's `secrets: '.env.dev' / '.env.prod'` (cloudflare adapter) —
187
- never committed, never defaulted. Fail fast for root-of-trust values; allow
188
- multi-name fallbacks only for developer convenience, never silently in prod.
189
- - Both adapters ship secrets from `secrets: '.env.<env>'` — cloudflare via
190
- wrangler secrets, astrale (managed) via the encrypted per-install store.
191
-
192
- Reliability rules (learned from the admin provisioning engine):
193
- - **Order effects**: external call FIRST, graph write AFTER when possible — a
194
- failed upstream then leaves nothing to clean up. When you must write first
195
- (reservations), write an INTENT record (state: 'provisioning'), make the
196
- external call idempotent (tags/external ids so re-entry ADOPTS instead of
197
- duplicating), and compensate on failure.
198
- - **Sagas for multi-step flows**: ordered phases; record state transitions on
199
- nodes (`provisioning → ready | failed`); compensation runs best-effort in
200
- reverse and NEVER throws (each step returns ok/skipped/error). Document
201
- which phases are safe to re-enter.
202
- - **Idempotency before mutation**: check existing state before writing; design
203
- re-runs of any handler to converge, not duplicate (postInstall seeds
204
- especially — they re-run on every reinstall).
205
-
206
- Anti-patterns (all observed in production code — don't):
207
- - constructing clients at module load, or monkey-patching `globalThis.fetch`;
208
- - scattering kernel-error message matching — the kernel exposes no stable
209
- error codes to handlers yet, so when you must match (the PATH_CONFLICT
210
- upsert), isolate it in ONE helper (the scaffold's `isPathConflict`) and
211
- treat it as tech debt;
212
- - god persistence files — split graph CRUD by entity;
213
- - leaving state fields mid-transition with no failure path (`'installing'`
214
- forever after a crash);
215
- - stringly-typed prop keys or class paths (always compiled accessors).
216
-
217
- ## 5 · Views & standalone functions (webhooks)
218
-
219
- - `defineView({ auth, mount: '/ui/contact', viewFor: selfOf(Contact) })` in
220
- `views/`, collected into the `views` map in `views/index.ts`, which
221
- `astrale.config.ts` imports and passes to `defineDomain({ views })`. The MAP
222
- KEY is the view's node slug (`'ui-contact'` → `/<origin>/core/views/ui-contact`).
223
- Installs a View
224
- node whose binding URL = `<serving url><mount>` (managed:
225
- `https://<slug>.svc.<region>.astrale.ai/ui/contact`). The client/ SPA
226
- serves `/ui/*` — BOTH adapters ship it (cloudflare via Workers Assets;
227
- managed via the platform's per-version asset archive); the SPA must handle
228
- any `/ui/<route>` via its fallback (the scaffold's vite config does).
229
- - Discovery: clients/GUIs find a node's views via
230
- `kernel.call('/kernel.astrale.ai/class.View/resolve', { node: <path> })` →
231
- `[{ path, url, name, origin }]` — `url` is the mounted iframe target.
232
- - `functions/` declares standalone callables (`defineRemoteFunction`) not
233
- attached to a class — each serves at `POST <worker>/functions/<slug>` and
234
- materializes a Function node under `/<origin>/core/functions/`. This is the
235
- INBOUND integration surface (webhooks).
236
-
237
- **The webhook-that-writes pattern** (validated): keep `auth: 'required'` and
238
- configure the external system's auth header with a minted delegation token —
239
- `astrale token --audience <service url> --ttl <seconds> --raw` (use a
240
- dedicated, attenuated identity: grant it only what the webhook needs).
241
- For `auth: 'public'` upstreams that can't carry a header (HMAC-signature
242
- webhooks, Stripe-style): `kernel` is null, but `ctx.selfKernel()` gives a
243
- session authenticated as THE FUNCTION'S OWN identity (its grants only) —
244
- VERIFY THE UPSTREAM SIGNATURE FIRST, then act as yourself. Needs
245
- `deps.INSTANCE_KERNEL_URL` (managed deploys set it) and the function identity
246
- granted exactly what it writes (e.g. EDIT on the target folder + USE on
247
- createNode + USE on the class — narrow, never root).
248
-
249
- **A PUBLIC view that reads the graph** (e.g. a server-rendered list page):
250
- the render ctx exposes `ctx.selfKernel()` (sdk ≥0.1.5) — a session as THE
251
- VIEW'S OWN identity. Read with it directly in `render`, template to HTML:
252
- `const k = await ctx.selfKernel(); const rows = await k.call('/messages::listChildren', {})`.
253
- Grant the view's function identity the READ-side minimum in your seed —
254
- the grant call is dispatched ON the identity node, with a bitmask:
255
- ```ts
256
- import { READ, USE, toMask } from '@astrale-os/kernel-core'
257
- await kernel.call('/<origin>/core/views/<view-slug>::grantPerm', { node: '/messages', perms: toMask(READ) })
258
- await kernel.call('/<origin>/core/views/<view-slug>::grantPerm', { node: '/:kernel.astrale.ai:interface.Container:listChildren', perms: toMask(USE) })
259
- ```
260
- Needs `deps.INSTANCE_KERNEL_URL` — managed deploys set it automatically; on
261
- the cloudflare adapter set it yourself: the instance's kernel API base is
262
- `https://<instance-slug>.eu.astrale.ai/api` (a vars entry or secret).
263
- Public-input
264
- hygiene: HTML-escape every stored string at render. (Prefer `selfKernel`
265
- over the `SELF` service binding for graph reads — `SELF` exists on both
266
- cloudflare and current managed runtimes, but it costs an extra HTTP hop and
267
- older hosts omit it.)
268
-
269
- **Calling a remote function over raw HTTP**: the response wraps your return
270
- value in an envelope — `{ result: <your value> }` (errors: `{ error }`).
271
-
272
- **Webhook idempotency** (senders retry — design for replays): derive a
273
- DETERMINISTIC node path from the sender's id (`/contacts/lead-<externalId>`),
274
- and make that key BOTH the existence check AND the write target. The classic
275
- bug is checking one key and writing another (random-suffixed) — the replay
276
- then duplicates. Custom `binding` supports REST-ish routes + header/body
277
- capture when the sender's shape is fixed (see distribution's proxy functions).
278
-
279
- ## 6 · Identity, auth, permissions
280
-
281
- - Your worker IS an identity: at install, every callable gets `(iss = serving
282
- URL, sub = function path)` stamped in the graph; the kernel verifies your
283
- worker's signatures against your live JWKS. `astrale.config.ts` origin =
284
- `schema.domain` (aliasing another origin triggers a DANGER prompt).
285
- - Inbound calls carry a delegation of the caller; your handler's `kernel` acts
286
- with `union(caller's delegated authority, your function's own grants)` —
287
- attenuation is automatic (you can't exceed what the caller + you hold).
288
- - `authorize` hook: return `undefined` to allow (relying on kernel-level
289
- checks downstream), or assert claims/perms before `execute` runs.
290
- - Permissions are bitmasks (READ/EDIT/USE/SHARE) on `has_perm` edges; grants
291
- on a node cascade down the tree. Function identities get USE on
292
- `mintDelegationCredential` at install (enables cross-function calls).
293
-
294
- ## 7 · Deploy & install
295
-
296
- Adapter choice in `astrale.config.ts` (each adapter is its OWN package — swap
297
- BOTH the import and the call):
298
- - `cloudflare({...})` from `@astrale-os/adapter-cloudflare` — your CF account;
299
- `dev` (wrangler dev), `prod` (route or workers.dev); ships secrets + SPA
300
- assets; extra bindings via a deep-merged `wrangler` block.
301
- - `astrale({ dev: {...}, prod: { instance: '<slug>' } })` from
302
- `@astrale-os/adapter-astrale` — managed (the scaffold's DEFAULT): publishes
303
- the bundle THROUGH the platform and installs it as a host-local service next
304
- to your instance (`https://<name>-<hash>.svc.<region>.astrale.ai`). No CF
305
- account; auth = your `astrale auth login` session. Ships the client SPA
306
- (`/ui` serves managed) and author secrets (`prod.secrets: '.env.prod'` —
307
- encrypted at rest platform-side, re-applied on redeploys; omit = keep,
308
- `{}` = clear; platform keys always win).
309
-
310
- The first deploy generates `.astrale/identity.ts` — the domain's SIGNING
311
- IDENTITY (its private key). Losing or regenerating it breaks reinstalls under
312
- the same origin; back it up (or commit it knowingly for throwaway domains).
313
-
314
- Dev-loop reality: `pnpm dev` serves on localhost — a REMOTE instance's kernel
315
- cannot fetch its install bundle. Local URL installs only work against a kernel
316
- that can reach you (local kernel, or a tunnel). Against a managed/remote
317
- instance, iterate with `pnpm prod` (the managed loop is ~25s and keeps the
318
- service URL stable).
319
-
320
- `postInstall` runs after every install — MUST be a colon MethodPath
321
- (`/:origin:class.X:seed`; tree paths are rejected) and MUST be idempotent
322
- (catch path-conflicts). Seed folders, defaults, and demo data here.
323
-
324
- Manual install of any served domain: `astrale domain install <url> --direct`.
325
-
326
- The managed catalog surface (what `pnpm prod` shells into) is callable
327
- directly — useful for recovery and inspection:
328
- `/admin/domains/<name>::install {instanceId, source:{kind:'package'}}` ·
329
- `::uninstall {instanceId}` (the un-wedge recipe before a retry) ·
330
- `::installations` · `::versions` — all on the admin instance (`-i admin`).
331
-
332
- ## 8 · Testing
333
-
334
- - **Logic**: unit-test with fake ports (the DI in §4 exists for this) and a
335
- fake kernel — an in-memory `{ call(path, params) }` that records calls /
336
- returns canned nodes. Assert call ORDER and failure paths (best-effort
337
- flows: one failing step must not block the rest).
338
- - **Wiring/contract**: typecheck is the contract test (`pnpm typecheck` /
339
- `tsgo --noEmit`); the schema drives param validation at runtime.
340
- - **Live smoke** (always finish with this): deploy, then drive the REAL surface
341
- with the CLI — create a node, call an instance method, walk an edge, call a
342
- cross-domain function. Fixture-green alone proves nothing about dispatch,
343
- identity, or bindings.
344
-
345
- ## 9 · Debugging quick table
346
-
347
- | Symptom | Likely cause |
348
- |---|---|
349
- | any managed-service 500 — `{"error":{"code":5000,"message":"internal error; reference = …"}}` | `astrale logs <service-slug>` tails the service's runtime buffer (console output, 5xx accesses, uncaught exception stacks) — the slug is the first label of the `…svc.<region>.astrale.ai` URL `pnpm prod` printed. Services deployed before log capture need one redeploy first. |
350
- | `Permission denied: EDIT on /x (param-target)` | `/x` doesn't exist — seed the parent |
351
- | `method "x" not found … call it as "/:o:class.C/x"` | instance form used for a static method |
352
- | `Delegation mint failed for <url>` | check `--debug` cause chain; worker→worker call machinery |
353
- | postInstall `not within origin` | tree path used — switch to `/:origin:class.X:method` |
354
- | install: `missing remote binding` | a callable lacks `binding.remoteUrl` — build via the adapter, not hand-rolled specs |
355
- | `ERR_PNPM_IGNORED_BUILDS` / approve-builds on `pnpm run` | template's pnpm-workspace.yaml needs `ignoredBuiltDependencies` + `verifyDepsBeforeRun: false` (recent scaffolds have it) |
356
- | stale/broken package versions on scaffold | clear pnpm metadata cache; check template floors are current |
357
- | TS: "Types of property '__brand' are incompatible" in untouched files | TWO copies of kernel-core/dsl in the tree (mixed link:/registry/override resolution) — unify versions, then `pnpm dedupe` |
358
- | runtime 500: `MISSING_DEF: Def at path "…" is not registered` | SAME dual-copy disease at runtime (defineSchema wrote copy A, compile read copy B) — and on managed installs it presents as the service never turning ready / install stuck `installing`. Unify + dedupe. |
359
- | managed install stuck at `installing` | check the SERVICE first: `curl <svc>/meta` (500 = the bundle itself is broken — run it locally with wrangler to see the real error); then `…::uninstall {instanceId}` and re-run the install |
360
- | `Path not found: /admin/instances/<uuid>` on install | `instanceId` takes the instance SLUG, not the node UUID (`astrale instance status <slug>` shows both) |
361
- | deploy: `Export named 'X' not found` from an @astrale-os module | a transitive dep resolved below its REAL floor — `pnpm add @astrale-os/<pkg>@<needed>` then `pnpm dedupe` |
362
-
363
- Use `astrale call <path> --describe` for any callable's schema, `--debug` for
364
- the full error chain, `curl <worker>/meta` for what a worker serves
365
- (domainName, schemaHash), and `astrale logs <service-slug> [--tail N]` for a
366
- managed service's runtime logs.
367
-
368
- ## Related skills
369
- - **astrale-cli** — every CLI command (auth, instances, calls, install).
370
- - **astrale-live-domain-edit** — graph-level schema surgery on a live kernel
371
- (no worker changes): temp specs, reinstall semantics, minimum class/method
372
- graph material.
@@ -1,8 +0,0 @@
1
- /**
2
- * StatusPage operations — one file per method, assembled here for the
3
- * composition root (`runtime/index.ts`). Transport-agnostic logic over a
4
- * `CallableKernel`, delegating the roll-up decision to `core/monitor`.
5
- */
6
- export { add } from './add'
7
- export { check } from './check'
8
- export { create } from './create'