@impetik/xeer-mcp 0.2.17 → 0.2.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impetik/xeer-mcp",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "type": "module",
5
5
  "description": "Model Context Protocol server for Xeer: the scaffold, check, dev, build, and deploy loop as agent tools.",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "dependencies": {
48
48
  "@modelcontextprotocol/sdk": "^1.29.0",
49
49
  "zod": "^4.0.10",
50
- "@impetik/xeer": "0.2.17"
50
+ "@impetik/xeer": "0.2.19"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^24.1.0"
@@ -481,7 +481,7 @@ declare const actions: readonly [{
481
481
  readonly command: readonly ["inspect"];
482
482
  readonly summary: "Read a running application manifest.";
483
483
  readonly description: string;
484
- readonly usage: readonly ["inspect <local-url|app> [--environment prod|preview] [--control-url <url>] [--json]"];
484
+ readonly usage: readonly ["inspect <local-url|app|directory> [--environment prod|preview] [--control-url <url>] [--json]"];
485
485
  readonly helpOrder: 230;
486
486
  readonly outputProtocol: "xeer.command.v0";
487
487
  readonly effects: readonly ["network-read", "read-state"];
@@ -874,7 +874,7 @@ declare const actions: readonly [{
874
874
  readonly id: "preview";
875
875
  readonly command: readonly ["preview"];
876
876
  readonly summary: "Run a verified artifact in a local preview server.";
877
- readonly usage: readonly ["preview [directory] [--host <host>] [--port <port>] [--json]"];
877
+ readonly usage: readonly ["preview [directory] [--host <host>] [--port <port>] [--force] [--json]"];
878
878
  readonly helpOrder: 240;
879
879
  readonly outputProtocol: "xeer.dev.v0";
880
880
  readonly effects: readonly ["read-source", "write-generated", "run-local", "write-state"];
@@ -892,7 +892,7 @@ declare const actions: readonly [{
892
892
  readonly command: readonly ["state"];
893
893
  readonly summary: "Read record counts from a running application.";
894
894
  readonly description: string;
895
- readonly usage: readonly ["state <local-url|app> [--environment prod|preview] [--control-url <url>] [--json]"];
895
+ readonly usage: readonly ["state <local-url|app|directory> [--environment prod|preview] [--control-url <url>] [--json]"];
896
896
  readonly helpOrder: 260;
897
897
  readonly outputProtocol: "xeer.command.v0";
898
898
  readonly effects: readonly ["network-read", "read-state"];
@@ -927,7 +927,7 @@ declare const actions: readonly [{
927
927
  readonly command: readonly ["logs"];
928
928
  readonly summary: "Read the bounded log ring of a running application.";
929
929
  readonly description: string;
930
- readonly usage: readonly ["logs <local-url|app> [--environment prod|preview] [--after <cursor>] [--control-url <url>] [--json]"];
930
+ readonly usage: readonly ["logs <local-url|app|directory> [--environment prod|preview] [--after <cursor>] [--control-url <url>] [--json]"];
931
931
  readonly helpOrder: 280;
932
932
  readonly outputProtocol: "xeer.command.v0";
933
933
  readonly effects: readonly ["network-read", "read-state"];
@@ -1014,7 +1014,7 @@ declare const actions: readonly [{
1014
1014
  readonly id: "auth.as";
1015
1015
  readonly command: readonly ["auth", "as"];
1016
1016
  readonly summary: "Select a deterministic local development persona.";
1017
- readonly usage: readonly ["auth as <alice|bob> [directory] [--workspace <id>]... [--json]"];
1017
+ readonly usage: readonly ["auth as <alice|bob|carol> [directory] [--workspace <id>]... [--role <name>]... [--permission <name>]... [--json]"];
1018
1018
  readonly helpOrder: 200;
1019
1019
  readonly outputProtocol: "xeer.command.v0";
1020
1020
  readonly effects: readonly ["write-state"];
@@ -75,7 +75,9 @@ const MCP_PRESENTATION = {
75
75
  'Each failed case carries a Diagnostic-shaped failure with matcher, expected, actual, and the',
76
76
  'project-relative test location: XE1904 assertion, XE1905 threw or unexpectedly refused call,',
77
77
  'XE1906 timeout. Compiler diagnostics abort the run before any test boots.',
78
- 'Tests call as(\'alice\'|\'bob\'|\'guest\'), so ownership and authorization are directly testable.',
78
+ 'Tests call as(\'alice\'|\'bob\'|\'carol\'|\'guest\'), optionally with { workspaceIds, roles, permissions },',
79
+ 'so ownership and authorization are directly testable. A persona holding none of the three is',
80
+ 'kind: "guest" and authenticated() refuses it.',
79
81
  ].join(' '),
80
82
  },
81
83
  new: {
@@ -340,7 +342,8 @@ const actions = [
340
342
  description: 'Reads a local dev or preview URL directly. For a deployed application, Xeer uses the '
341
343
  + 'signed-in builder credential and defaults names and app IDs to production; pass an exact URL or '
342
344
  + '--environment preview to select preview.',
343
- usage: ['inspect <local-url|app> [--environment prod|preview] [--control-url <url>] [--json]'], helpOrder: 230,
345
+ usage: ['inspect <local-url|app|directory> [--environment prod|preview] [--control-url <url>] [--json]'],
346
+ helpOrder: 230,
344
347
  outputProtocol: 'xeer.command.v0', effects: ['network-read', 'read-state'], idempotent: true,
345
348
  reversible: true, destructive: false, humanPrerequisites: [], pathPolicy: 'app-or-url',
346
349
  surfaces: {
@@ -531,7 +534,7 @@ const actions = [
531
534
  },
532
535
  {
533
536
  id: 'preview', command: ['preview'], summary: 'Run a verified artifact in a local preview server.',
534
- usage: ['preview [directory] [--host <host>] [--port <port>] [--json]'], helpOrder: 240,
537
+ usage: ['preview [directory] [--host <host>] [--port <port>] [--force] [--json]'], helpOrder: 240,
535
538
  outputProtocol: 'xeer.dev.v0', effects: ['read-source', 'write-generated', 'run-local', 'write-state'],
536
539
  idempotent: false, reversible: true, destructive: false, humanPrerequisites: [],
537
540
  pathPolicy: 'project-relative', surfaces: { cli: true, mcpExclusion: NOT_EXPOSED_V0 },
@@ -541,7 +544,8 @@ const actions = [
541
544
  description: 'Reads a local dev or preview URL directly. For a deployed application, Xeer uses the '
542
545
  + 'signed-in builder credential and defaults names and app IDs to production; pass an exact URL or '
543
546
  + '--environment preview to select preview.',
544
- usage: ['state <local-url|app> [--environment prod|preview] [--control-url <url>] [--json]'], helpOrder: 260,
547
+ usage: ['state <local-url|app|directory> [--environment prod|preview] [--control-url <url>] [--json]'],
548
+ helpOrder: 260,
545
549
  outputProtocol: 'xeer.command.v0', effects: ['network-read', 'read-state'], idempotent: true,
546
550
  reversible: true, destructive: false, humanPrerequisites: [], pathPolicy: 'app-or-url',
547
551
  surfaces: { cli: true, mcpExclusion: NOT_EXPOSED_V0 },
@@ -559,7 +563,7 @@ const actions = [
559
563
  description: 'Reads a local dev or preview URL directly. For a deployed application, Xeer uses the '
560
564
  + 'signed-in builder credential and defaults names and app IDs to production; pass an exact URL or '
561
565
  + '--environment preview to select preview.',
562
- usage: ['logs <local-url|app> [--environment prod|preview] [--after <cursor>] [--control-url <url>] [--json]'],
566
+ usage: ['logs <local-url|app|directory> [--environment prod|preview] [--after <cursor>] [--control-url <url>] [--json]'],
563
567
  helpOrder: 280, outputProtocol: 'xeer.command.v0', effects: ['network-read', 'read-state'],
564
568
  idempotent: true, reversible: true, destructive: false, humanPrerequisites: [], pathPolicy: 'app-or-url',
565
569
  surfaces: { cli: true, mcpExclusion: NOT_EXPOSED_V0 },
@@ -607,7 +611,8 @@ const actions = [
607
611
  },
608
612
  {
609
613
  id: 'auth.as', command: ['auth', 'as'], summary: 'Select a deterministic local development persona.',
610
- usage: ['auth as <alice|bob> [directory] [--workspace <id>]... [--json]'], helpOrder: 200,
614
+ usage: ['auth as <alice|bob|carol> [directory] [--workspace <id>]... [--role <name>]... [--permission <name>]... [--json]'],
615
+ helpOrder: 200,
611
616
  outputProtocol: 'xeer.command.v0', effects: ['write-state'], idempotent: true,
612
617
  reversible: true, destructive: false, humanPrerequisites: [], pathPolicy: 'project-relative',
613
618
  surfaces: { cli: true, mcpExclusion: NOT_EXPOSED_V0 },
@@ -19,6 +19,7 @@
19
19
  * `INSERT OR IGNORE`, so a retried request writes the same id and changes nothing, and the response
20
20
  * says whether this call was the one that created it. No request ledger, no dedupe window.
21
21
  */
22
+ import { decodePageCursor, encodePageCursor } from './page-cursor.js';
22
23
  import type { EncodedValue } from './value.js';
23
24
  export declare const ADMIN_PROTOCOL: "xeer.admin.v0";
24
25
  /** Columns the runtime owns on every table. Present in every row, never writable by a client. */
@@ -101,10 +102,11 @@ export interface AdminWriteResponseV0 {
101
102
  * A page cursor. Opaque to clients by contract, and deliberately not signed: it names a position in
102
103
  * the caller's own data on a surface they already had to authenticate to reach, so there is nothing
103
104
  * for a forged one to disclose that the next page would not have shown anyway.
105
+ *
106
+ * The console and `ctx.db.table(…).page(…)` seek the same `(created_at, id)` keyset over the same
107
+ * tables, so they share one codec rather than keeping two that could drift into disagreeing about
108
+ * what a token means. The names stay because the admin protocol names them.
104
109
  */
105
- export declare function encodeAdminCursor(createdAt: string, id: string): string;
110
+ export declare const encodeAdminCursor: typeof encodePageCursor;
106
111
  /** The inverse, or `null` when the text is not a cursor this version wrote. */
107
- export declare function decodeAdminCursor(cursor: string): {
108
- createdAt: string;
109
- id: string;
110
- } | null;
112
+ export declare const decodeAdminCursor: typeof decodePageCursor;
@@ -19,6 +19,7 @@
19
19
  * `INSERT OR IGNORE`, so a retried request writes the same id and changes nothing, and the response
20
20
  * says whether this call was the one that created it. No request ledger, no dedupe window.
21
21
  */
22
+ import { decodePageCursor, encodePageCursor } from './page-cursor.js';
22
23
  export const ADMIN_PROTOCOL = 'xeer.admin.v0';
23
24
  /** Columns the runtime owns on every table. Present in every row, never writable by a client. */
24
25
  export const ADMIN_RUNTIME_FIELDS = Object.freeze([
@@ -33,26 +34,11 @@ export const ADMIN_PAGE_MAX = 200;
33
34
  * A page cursor. Opaque to clients by contract, and deliberately not signed: it names a position in
34
35
  * the caller's own data on a surface they already had to authenticate to reach, so there is nothing
35
36
  * for a forged one to disclose that the next page would not have shown anyway.
37
+ *
38
+ * The console and `ctx.db.table(…).page(…)` seek the same `(created_at, id)` keyset over the same
39
+ * tables, so they share one codec rather than keeping two that could drift into disagreeing about
40
+ * what a token means. The names stay because the admin protocol names them.
36
41
  */
37
- export function encodeAdminCursor(createdAt, id) {
38
- const text = JSON.stringify([createdAt, id]);
39
- return btoa(String.fromCharCode(...new TextEncoder().encode(text)))
40
- .replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
41
- }
42
+ export const encodeAdminCursor = encodePageCursor;
42
43
  /** The inverse, or `null` when the text is not a cursor this version wrote. */
43
- export function decodeAdminCursor(cursor) {
44
- try {
45
- const padded = cursor.replaceAll('-', '+').replaceAll('_', '/');
46
- const bytes = Uint8Array.from(atob(padded + '='.repeat((4 - (padded.length % 4)) % 4)), (c) => c.charCodeAt(0));
47
- const parsed = JSON.parse(new TextDecoder().decode(bytes));
48
- if (!Array.isArray(parsed) || parsed.length !== 2)
49
- return null;
50
- const [createdAt, id] = parsed;
51
- if (typeof createdAt !== 'string' || typeof id !== 'string')
52
- return null;
53
- return { createdAt, id };
54
- }
55
- catch {
56
- return null;
57
- }
58
- }
44
+ export const decodeAdminCursor = decodePageCursor;
@@ -143,39 +143,61 @@ export const DIAGNOSTIC_DEFINITIONS = [
143
143
  define('XE1003', 'The manifest carries a property the application-source schema does not define.', 'Remove the property, or fix its spelling. The manifest is closed: unknown keys are never ignored.', CHECKED_BY_EVERY_COMPILE),
144
144
  define('XE1101', 'An entrypoint path is absolute or escapes the project root.', 'Use a forward-slash path relative to xeer.app.json that stays inside the project.', CHECKED_BY_EVERY_COMPILE),
145
145
  define('XE1201', 'A source import breaks the zone boundary: it is absolute, leaves the project root, '
146
- + 'reaches the opposite entrypoint, or the module is reachable from both graphs while living outside '
147
- + 'src/shared/.', 'Keep imports relative and inside the project. Move genuinely shared code to src/shared/ and update '
148
- + 'both importers; never import one entrypoint from the other.', CHECKED_BY_EVERY_COMPILE),
149
- define('XE1202', 'A package import violates the zone\'s import policy structurally: a Node builtin, '
150
- + 'a platform subpath the zone does not admit, a renderer-family entrypoint that is test-only, '
151
- + 'metadata, or unknown to the selected runtime source, or (server zone) any npm package — the '
152
- + 'server zone is closed.', `The client zone admits ${CLIENT_IMPORT_POLICY.platformModules.join(', ')}, the platform-managed `
153
- + 'renderer surface (preact and its supported entrypoints, plus React-ecosystem imports aliased '
154
- + 'onto preact/compat), and any npm package declared in package.json dependencies and installed. '
155
- + `The server zone admits only ${SERVER_IMPORT_POLICY.platformModules.join(', ')}. Node builtins `
156
- + 'are never importable. Move the code to the zone that owns it instead of widening the import.', CHECKED_BY_EVERY_COMPILE),
146
+ + 'reaches the opposite entrypoint, or pulls a module out of the other zone\'s graph. A cross-zone '
147
+ + 'import is reported at the import that admitted the module, not at the modules it dragged in — '
148
+ + 'those are correct where they live. A module both graphs reach with no zone-exclusive import '
149
+ + 'anywhere beneath it names no offending side, and is reported on the module itself.', 'Keep imports relative and inside the project; never import one entrypoint from the other. For a '
150
+ + 'cross-zone import, delete the import the message points at: move code both zones need to '
151
+ + 'src/shared/ and import it from there, or expose the data through a query, mutation, or endpoint '
152
+ + 'and call it from the client. Do not remove the zone imports from the modules it reached.', CHECKED_BY_EVERY_COMPILE),
153
+ define('XE1202', 'A package import violates the zone\'s import policy structurally: a Node builtin '
154
+ + 'or a native addon (.node) in any zone, a platform subpath the zone does not admit, a '
155
+ + 'renderer-family entrypoint that is test-only, metadata, or unknown to the selected runtime '
156
+ + 'source, or a package family the other zone owns — the renderer (preact, react, react-dom, '
157
+ + 'scheduler) in the server zone. It is not reported for an ordinary npm package in either zone: '
158
+ + 'both admit any declared and installed dependency.', `The client zone admits ${CLIENT_IMPORT_POLICY.platformModules.join(', ')} and the `
159
+ + 'platform-managed renderer surface (preact and its supported entrypoints, plus '
160
+ + 'React-ecosystem imports aliased onto preact/compat); the server zone admits '
161
+ + `${SERVER_IMPORT_POLICY.platformModules.join(', ')}. Both zones additionally admit any npm `
162
+ + 'package declared in package.json dependencies and installed. Node builtins and native addons '
163
+ + 'are never importable anywhere: the client runs in a browser and the server runs in workerd, '
164
+ + 'neither of which has them — that is a runtime fact, not an allowlist. Move the code to the '
165
+ + 'zone that owns the import, or use a dependency that targets browsers and workers.', CHECKED_BY_EVERY_COMPILE),
157
166
  define('XE1203', 'A dynamic import() specifier is not a string literal.', 'Use a literal specifier so the module graph stays statically knowable, or a static import.', CHECKED_BY_EVERY_COMPILE),
158
167
  define('XE1204', 'A relative source import does not resolve to a file.', 'Write the specifier without an extension, or with the file\'s real one: the resolver appends '
159
168
  + 'candidate extensions rather than rewriting them, so ./shared/title.js does not find '
160
169
  + 'shared/title.ts. Directory imports resolve to index.*.', CHECKED_BY_EVERY_COMPILE),
161
170
  define('XE1205', 'TypeScript reported an error in a file reachable from an entrypoint. The message '
162
171
  + 'starts with the TS#### code and span points at the exact position.', 'Fix the type error. Operation input and result types come from the generated contract, so a mismatch '
163
- + 'between a client call and its server handler surfaces here.', CHECKED_BY_EVERY_COMPILE),
172
+ + 'between a client call and its server handler surfaces here. A handler whose result the platform '
173
+ + 'cannot encode is reported on the handler: declare result shapes with a `type` alias rather than an '
174
+ + '`interface`, which TypeScript never gives an implicit index signature.', CHECKED_BY_EVERY_COMPILE),
164
175
  define('XE1206', 'A stylesheet is imported outside the client graph.', 'Import .css only from client modules; the server bundle has no styling stage.', CHECKED_BY_EVERY_COMPILE),
165
- define('XE1207', 'A client-zone import names an npm package that package.json does not declare in '
176
+ define('XE1207', 'An import names an npm package that package.json does not declare in '
166
177
  + '"dependencies". It may still resolve through hoisting, which is exactly the accident the rule '
167
- + 'exists to prevent.', 'Add the package to the application\'s package.json "dependencies" and install it. The client '
168
- + 'zone may import any declared and installed npm package; type-only imports may come from '
178
+ + 'exists to prevent. Reported in both zones: the message names the zone the import was written '
179
+ + 'in, but the rule is the same one.', 'Add the package to the application\'s package.json "dependencies" and install it. Either zone '
180
+ + 'may import any declared and installed npm package; type-only imports may come from '
169
181
  + 'devDependencies instead.', CHECKED_BY_EVERY_COMPILE),
170
- define('XE1208', 'A client-zone import names a declared dependency that is not installed: nothing '
171
- + 'in a reachable node_modules provides it.', 'Run your package manager\'s install (npm install / pnpm install) so the declared dependency is '
182
+ define('XE1208', 'An import names a declared dependency that is not installed: nothing '
183
+ + 'in a reachable node_modules provides it. Reported in both zones.', 'Run your package manager\'s install (npm install / pnpm install) so the declared dependency is '
172
184
  + 'present on disk, then check again.', CHECKED_BY_EVERY_COMPILE),
185
+ define('XE1209', 'A subresource — an image, a font, or a wasm module — is imported outside the '
186
+ + 'client graph. The declarations that give such an import a type are program-global, so it '
187
+ + 'type-checks in every zone, but only the client bundler has a loader that turns one into a URL.', 'Import images, fonts, and wasm only from client modules; the server bundle has no asset stage. '
188
+ + 'A server handler that needs the bytes should read them from public/ or receive the URL from '
189
+ + 'the client.', CHECKED_BY_EVERY_COMPILE),
173
190
  define('XE1300', 'The default server export is not a statically inspectable defineServer({...}) call, '
174
191
  + 'or an operation group is not a literal object.', 'Export `default defineServer({ queries: {...}, mutations: {...}, endpoints: {...} })` with literal '
175
192
  + 'object members: no spreads, shorthand, computed keys, or wrappers.', CHECKED_BY_EVERY_COMPILE),
176
193
  define('XE1301', 'Two operations in the same group share a name.', 'Rename one of them, or delete the duplicate registration if it was a copy-paste.', CHECKED_BY_EVERY_COMPILE),
177
194
  define('XE1302', 'An endpoint key is not `METHOD /api/path`.', 'Use an uppercase HTTP method, one space, and a literal /api path; dynamic segments are :name.', CHECKED_BY_EVERY_COMPILE),
178
- define('XE1303', 'A query or mutation name is not namespaced.', 'Use a dotted lowercase name such as notes.list.', CHECKED_BY_EVERY_COMPILE),
195
+ define('XE1303', 'A query or mutation name does not match the operation-name grammar: two or more '
196
+ + 'dot-separated segments, each starting with a lowercase letter and continuing with lowercase '
197
+ + 'letters, digits, `_`, or `-`.', 'Write the name as `namespace.name` with no uppercase letter anywhere — uppercase is the '
198
+ + 'restriction authors do not expect, so `posts.bySlug` is refused even though it is namespaced. '
199
+ + 'Separate words with `_` or `-` instead: notes.list, notes.list_pinned, notes.by-slug, and '
200
+ + 'notes.pinned.v2 are all accepted. Formally `^[a-z][a-z0-9_-]*\\.[a-z][a-z0-9_.-]*$`.', CHECKED_BY_EVERY_COMPILE),
179
201
  define('XE1304', 'Two endpoints reduce to the same route shape, so dispatch would be ambiguous.', 'Change the method or a static path segment. Parameter names do not distinguish routes: '
180
202
  + 'GET /api/notes/:id and GET /api/notes/:slug are the same route.', CHECKED_BY_EVERY_COMPILE),
181
203
  define('XE1401', 'A compiled module (10 MiB) or an asset (25 MiB) exceeds the v0 limit.', 'Shrink the module or asset. There is no flag that raises a v0 limit.', BUILT_BY_EVERY_BUILD),
@@ -194,12 +216,14 @@ export const DIAGNOSTIC_DEFINITIONS = [
194
216
  + 'packages to reconsider before the error tier is reached.', BUILT_BY_EVERY_BUILD),
195
217
  define('XE1501', 'Bundling failed after the module graph and types were accepted.', 'Read the bundler message in `message`. It usually names a syntax construct the target does not '
196
218
  + 'support, rather than a Xeer rule.', BUILT_BY_EVERY_BUILD),
197
- define('XE1502', 'A bundled client dependency imports a Node builtin, so the bundle cannot run in a '
198
- + 'browser. The message names the dependency and the builtin; the offending import sits inside '
199
- + 'the package, not in application code. The diagnostic is located at the application import '
200
- + 'that admitted the package into the client graph.', 'Use a browser-targeted package (or the package\'s browser entrypoint) instead. The client zone '
201
- + 'has no Node builtins to offer, so the platform refuses the bundle rather than shipping one '
202
- + 'that throws at load time.', BUILT_BY_EVERY_BUILD),
219
+ define('XE1502', 'A bundled dependency imports a Node builtin, so the bundle cannot run where its '
220
+ + 'zone runs — a browser for the client zone, workerd for the server zone. The message names the '
221
+ + 'zone, the dependency and the builtin; the offending import sits inside the package, not in '
222
+ + 'application code. The diagnostic is located at the application import that admitted the '
223
+ + 'package into that zone\'s graph.', 'Use a package targeting browsers and workers, or the package\'s browser/worker entrypoint. '
224
+ + 'Neither zone has Node builtins to offer, so the platform refuses the bundle rather than '
225
+ + 'shipping one that throws at load time. This is a property of the dependency chain and so is '
226
+ + 'found at build, not at check.', BUILT_BY_EVERY_BUILD),
203
227
  define('XE1503', 'The client bundle would contain a second physical copy of the managed renderer. '
204
228
  + 'Bare renderer imports are pinned to the platform anchor, so a second copy means a path-based '
205
229
  + 'escape: a relative or absolute import reaching into some node_modules copy of the renderer.', 'Import the renderer by its bare specifier (preact, preact/hooks, react, …) so the platform pins '
@@ -276,8 +300,10 @@ export const DIAGNOSTIC_DEFINITIONS = [
276
300
  + "names: pass it with as({ name, workspaceIds }).", ['test']),
277
301
  define('XE1906', 'A test exceeded its 20 second budget. The remaining tests still run.', 'Remove the wait: tests run against a local runtime, so a timeout means an unresolved promise or an '
278
302
  + 'operation that never returns, not a slow machine.', ['test']),
279
- define('XE2001', 'An inspector command was invoked without a target.', 'Pass the preview URL from the dev/preview `preview.ready` event, or a deployed application name, '
280
- + 'appId, or URL.', ['inspect', 'state']),
303
+ define('XE2001', 'An inspector command was invoked without a target, or with a project directory in '
304
+ + 'which no `xeer dev` or `xeer preview` server is running — or in which both are, so the '
305
+ + 'directory does not name one of them.', 'Pass a project directory (`.`) to read the local server running for it, the URL from the '
306
+ + 'dev/preview `preview.ready` event, or a deployed application name, appId, or URL.', ['inspect', 'state']),
281
307
  define('XE2002', 'The inspector request itself failed: a local dev/preview inspector that answered '
282
308
  + 'a non-2xx status or no JSON, or a control plane that refused the proxied read, in which case '
283
309
  + 'the read never reached the application. An application that refused the read is XE2004.', 'Confirm the preview is still running and the URL matches the current `preview.ready` event. For a '
@@ -353,6 +379,11 @@ export const DIAGNOSTIC_DEFINITIONS = [
353
379
  define('XE5133', 'A closed-beta quota refused the environment-variable request: `xeer env set` claims '
354
380
  + 'the project on first use, so it is capped by the same apps-per-builder quota as `xeer deploy`.', 'Set the value on an app you already own — `xeer link` lists them — or ask the Xeer team to raise the '
355
381
  + 'quota. `message` states the quota, your usage, and any reset.', ['env']),
382
+ define('XE5134', 'A file exists at `.xeer/env.<environment>.local.json` but could not be used '
383
+ + 'in full: it is not a `xeer.env-local.v0` document, it could not be read, or some of its '
384
+ + 'entries have names or values a local run cannot load — so `ctx.env` is missing them.', 'Read `message`: it names the file and the exact problem — the shape to write is '
385
+ + '{"format": "xeer.env-local.v0", "values": {"NAME": "value"}} with UPPER_SNAKE_CASE names '
386
+ + 'and string values. `xeer env pull` writes the file correctly.', ['dev', 'preview', 'test']),
356
387
  define('XE5139', 'xeer env failed without a more specific code.', 'Read `message`; it is the underlying error verbatim.', ['env']),
357
388
  define('XE5140', 'An `xeer deployments` invocation is wrong: an unusable --limit, or a directory that '
358
389
  + 'declares no app identity.', 'Read `message`. --limit takes a positive integer up to 200. In a directory with no appId, run '
@@ -475,9 +506,18 @@ export function renderDiagnosticsReference() {
475
506
  ' file?: string; // project-relative POSIX path, never absolute',
476
507
  ' span?: { line: number; column: number; length?: number }; // 1-based',
477
508
  ' hint?: string; // suggested edit',
509
+ ' primary?: true; // this broke the generated contract — repair it first',
510
+ ' causedBy?: string; // a consequence of that code, not an independent error',
478
511
  '}',
479
512
  '```',
480
513
  '',
514
+ '`primary` and `causedBy` are optional and may be absent from an entire envelope; absence means nothing',
515
+ 'was attributed, not that a diagnostic is independent. When they are present, repair every `primary: true`',
516
+ 'diagnostic first and leave every `causedBy` one alone until the primaries are clean: a `causedBy`',
517
+ 'diagnostic sits in a file the broken contract poisoned, and editing that file makes the application',
518
+ 'worse. Re-run afterwards — whatever still reports is real and carries its own location. A run that',
519
+ 'found a contract failure emits its primaries first; sort on the field rather than relying on that.',
520
+ '',
481
521
  'The "Seen in" column lists the commands whose JSON can carry the code. `check` diagnostics also appear',
482
522
  'in `build` and `dev`, because both run the same manifest and analysis stages first.',
483
523
  ];
@@ -515,9 +555,20 @@ export function renderDiagnosticsDocsPage() {
515
555
  ' file?: string; // project-relative POSIX path, never absolute',
516
556
  ' span?: { line: number; column: number; length?: number }; // 1-based',
517
557
  ' hint?: string; // suggested edit',
558
+ ' primary?: true; // this broke the generated contract — repair it first',
559
+ ' causedBy?: string; // a consequence of that code, not an independent error',
518
560
  '}',
519
561
  '```',
520
562
  '',
563
+ 'The last two fields are optional and may be absent from an entire envelope; absence means nothing was',
564
+ 'attributed, not that a diagnostic is independent. A failure that invalidates the generated contract —',
565
+ 'a `defineServer` the compiler cannot read statically, a duplicate operation name, an operation result',
566
+ 'the platform cannot encode — makes every type derived from those operations wrong, so the files that',
567
+ 'consume the contract fill with `XE1205` type errors their authors never caused. Repair the',
568
+ '`primary: true` diagnostics first, ignore the `causedBy` ones until the primaries are clean, then',
569
+ 're-run: whatever still reports is real. Such a run emits its primaries ahead of the rest, so the',
570
+ 'diagnostic worth reading is also the first one printed.',
571
+ '',
521
572
  'Every command prints these as JSON with `--json`; see [Building with AI agents](/guides/agents).',
522
573
  'The "Seen in" column lists the commands whose output can carry the code. `check` diagnostics also',
523
574
  'appear in `build` and `dev`, because both run the same manifest and analysis stages first.',
@@ -59,16 +59,24 @@ export interface ZoneImportPolicy {
59
59
  * knowledge — the SDK module targets live beside its resolver, not in this declarative layer.
60
60
  */
61
61
  readonly platformModules: readonly string[];
62
- /** Singleton-sensitive families. The renderer on the client; empty on the server today. */
62
+ /** Singleton-sensitive families. The renderer on the client; empty on the server. */
63
63
  readonly managedFamilies: readonly ManagedPackageFamily[];
64
- /** The open set. `closed` reproduces the pre-#212 behavior exactly. */
64
+ /**
65
+ * Package families another zone owns, refused here whatever the open set says. The renderer on the
66
+ * server: `preact` is declared and installed in any application with a client, so once the server
67
+ * zone admits declared dependencies (#244) nothing else would stop a server module importing it,
68
+ * and the server has no DOM to render into. Zone-exclusive rather than structurally impossible —
69
+ * which is what makes it *evidence* for cross-zone attribution, where a Node builtin is not.
70
+ */
71
+ readonly foreignFamilies: readonly ManagedPackageFamily[];
72
+ /** The open set. `closed` admits nothing beyond the two lists above. */
65
73
  readonly applicationDependencies: {
66
74
  readonly mode: 'closed' | 'open';
67
75
  /** Bundler resolution conditions for this zone's application dependencies. */
68
76
  readonly conditions: readonly string[];
69
77
  /** Compile-time defines applied when bundling this zone. */
70
78
  readonly defines: Readonly<Record<string, string>>;
71
- /** Byte budgets over the zone's single-chunk bundle; `null` while the zone is closed. */
79
+ /** Byte budgets over the zone's single-chunk bundle; `null` when the zone declares none. */
72
80
  readonly bundleBudget: {
73
81
  readonly errorBytes: number;
74
82
  readonly advisoryBytes: number;
@@ -98,9 +106,22 @@ export declare const CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES: number;
98
106
  export declare const PREACT_RENDERER_FAMILY: ManagedPackageFamily;
99
107
  export declare const CLIENT_IMPORT_POLICY: ZoneImportPolicy;
100
108
  /**
101
- * The server zone: today's behavior, expressed as a policy value rather than a separate code path.
102
- * `mode: 'closed'` is byte-identical to the pre-#212 server allowlist; opening it later is a policy
103
- * decision plus a trust-model decision (#27), not new machinery.
109
+ * The server zone, open to application dependencies since ADR 0005 was accepted (#244).
110
+ *
111
+ * The two zones now state the same rule — declare it, install it, type it, use it — and differ only
112
+ * where the *runtime* differs. `workerd` conditions are declared because that is what the server
113
+ * executes in, so a package that publishes a worker build gets it. What stays refused on the server
114
+ * is refused for one of two reasons, and the distinction is load-bearing for both the diagnostic's
115
+ * wording and cross-zone attribution:
116
+ *
117
+ * - **Impossible**: Node builtins and native addons. workerd provides neither, and no policy change
118
+ * can conjure them. Refused in *every* zone, so it says nothing about which zone a module is in.
119
+ * - **Zone-exclusive**: the renderer family, and `@impetik/xeer/client*`. Perfectly loadable here;
120
+ * simply the other zone's surface. This is what attribution reads as evidence.
121
+ *
122
+ * No byte budget: unlike the client bundle, the server module is not shipped to a browser on every
123
+ * cold navigation, so the 10 MiB v0 module ceiling (XE1401) is the only bound the platform has a
124
+ * defensible number for. A per-zone budget is a product decision, not a gap this change may fill.
104
125
  */
105
126
  export declare const SERVER_IMPORT_POLICY: ZoneImportPolicy;
106
127
  export declare function zoneImportPolicy(zone: ImportZone): ZoneImportPolicy;
@@ -127,15 +148,21 @@ export type BareImportAdmission =
127
148
  | {
128
149
  kind: 'open';
129
150
  }
130
- /** Structurally rejected: a Node builtin, a foreign platform subpath, or a closed zone. */
151
+ /**
152
+ * Rejected on a zone rule rather than on the declaration invariant: a subpath of the platform
153
+ * package this zone does not admit, a package family another zone owns, or a zone that admits no
154
+ * application dependencies at all. Every one of these is *zone-exclusive* — the same specifier is
155
+ * legal somewhere — which is why they are attribution evidence and Node builtins are not.
156
+ */
131
157
  | {
132
158
  kind: 'rejected';
133
- reason: 'platform-subpath' | 'closed';
159
+ reason: 'platform-subpath' | 'foreign-family' | 'closed';
134
160
  };
135
161
  /**
136
- * Classifies one bare specifier under a zone's policy. Node builtins are the caller's concern (the
137
- * analyzer already owns that list); everything else — platform subpaths, managed families, and the
138
- * open set — is answered here so no consumer keeps a private copy of the rules.
162
+ * Classifies one bare specifier under a zone's policy. Node builtins and native addons are the
163
+ * caller's concern (the analyzer already owns those, and they are refused in every zone); everything
164
+ * else — platform subpaths, managed and foreign families, and the open set — is answered here so no
165
+ * consumer keeps a private copy of the rules.
139
166
  */
140
167
  export declare function admitBareImport(policy: ZoneImportPolicy, specifier: string): BareImportAdmission;
141
168
  /**
@@ -110,6 +110,7 @@ export const CLIENT_IMPORT_POLICY = Object.freeze({
110
110
  zone: 'client',
111
111
  platformModules: CLIENT_PLATFORM_MODULES,
112
112
  managedFamilies: Object.freeze([PREACT_RENDERER_FAMILY]),
113
+ foreignFamilies: Object.freeze([]),
113
114
  applicationDependencies: Object.freeze({
114
115
  mode: 'open',
115
116
  conditions: Object.freeze(['browser', 'import']),
@@ -121,17 +122,31 @@ export const CLIENT_IMPORT_POLICY = Object.freeze({
121
122
  }),
122
123
  });
123
124
  /**
124
- * The server zone: today's behavior, expressed as a policy value rather than a separate code path.
125
- * `mode: 'closed'` is byte-identical to the pre-#212 server allowlist; opening it later is a policy
126
- * decision plus a trust-model decision (#27), not new machinery.
125
+ * The server zone, open to application dependencies since ADR 0005 was accepted (#244).
126
+ *
127
+ * The two zones now state the same rule — declare it, install it, type it, use it — and differ only
128
+ * where the *runtime* differs. `workerd` conditions are declared because that is what the server
129
+ * executes in, so a package that publishes a worker build gets it. What stays refused on the server
130
+ * is refused for one of two reasons, and the distinction is load-bearing for both the diagnostic's
131
+ * wording and cross-zone attribution:
132
+ *
133
+ * - **Impossible**: Node builtins and native addons. workerd provides neither, and no policy change
134
+ * can conjure them. Refused in *every* zone, so it says nothing about which zone a module is in.
135
+ * - **Zone-exclusive**: the renderer family, and `@impetik/xeer/client*`. Perfectly loadable here;
136
+ * simply the other zone's surface. This is what attribution reads as evidence.
137
+ *
138
+ * No byte budget: unlike the client bundle, the server module is not shipped to a browser on every
139
+ * cold navigation, so the 10 MiB v0 module ceiling (XE1401) is the only bound the platform has a
140
+ * defensible number for. A per-zone budget is a product decision, not a gap this change may fill.
127
141
  */
128
142
  export const SERVER_IMPORT_POLICY = Object.freeze({
129
143
  zone: 'server',
130
144
  platformModules: SERVER_PLATFORM_MODULES,
131
145
  managedFamilies: Object.freeze([]),
146
+ foreignFamilies: Object.freeze([PREACT_RENDERER_FAMILY]),
132
147
  applicationDependencies: Object.freeze({
133
- mode: 'closed',
134
- conditions: Object.freeze([]),
148
+ mode: 'open',
149
+ conditions: Object.freeze(['worker', 'import']),
135
150
  defines: Object.freeze({}),
136
151
  bundleBudget: null,
137
152
  }),
@@ -145,9 +160,10 @@ export function packageNameOfSpecifier(specifier) {
145
160
  return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
146
161
  }
147
162
  /**
148
- * Classifies one bare specifier under a zone's policy. Node builtins are the caller's concern (the
149
- * analyzer already owns that list); everything else — platform subpaths, managed families, and the
150
- * open set — is answered here so no consumer keeps a private copy of the rules.
163
+ * Classifies one bare specifier under a zone's policy. Node builtins and native addons are the
164
+ * caller's concern (the analyzer already owns those, and they are refused in every zone); everything
165
+ * else — platform subpaths, managed and foreign families, and the open set — is answered here so no
166
+ * consumer keeps a private copy of the rules.
151
167
  */
152
168
  export function admitBareImport(policy, specifier) {
153
169
  if (policy.platformModules.includes(specifier))
@@ -169,6 +185,12 @@ export function admitBareImport(policy, specifier) {
169
185
  return { kind: 'managed-rejected', reason: 'metadata' };
170
186
  return { kind: 'managed', classification, target };
171
187
  }
188
+ // Before the open set, or an installed renderer would be admitted by the declaration invariant it
189
+ // satisfies in every application that has a client.
190
+ for (const family of policy.foreignFamilies) {
191
+ if (family.packages.includes(packageName))
192
+ return { kind: 'rejected', reason: 'foreign-family' };
193
+ }
172
194
  if (policy.applicationDependencies.mode === 'open')
173
195
  return { kind: 'open' };
174
196
  return { kind: 'rejected', reason: 'closed' };
@@ -21,3 +21,4 @@ export * from './network-policy.js';
21
21
  export * from './review.js';
22
22
  export * from './template.js';
23
23
  export * from './tunnel.js';
24
+ export * from './type-check-profile.js';
@@ -21,3 +21,4 @@ export * from './network-policy.js';
21
21
  export * from './review.js';
22
22
  export * from './template.js';
23
23
  export * from './tunnel.js';
24
+ export * from './type-check-profile.js';