@impetik/xeer-mcp 0.2.16 → 0.2.18

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.16",
3
+ "version": "0.2.18",
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.16"
50
+ "@impetik/xeer": "0.2.18"
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;
@@ -12,6 +12,7 @@
12
12
  * catalogue disagree in either direction, so a new code cannot ship
13
13
  * undocumented and a retired code cannot linger here.
14
14
  */
15
+ import { CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES, CLIENT_BUNDLE_ERROR_BUDGET_BYTES, CLIENT_IMPORT_POLICY, SERVER_IMPORT_POLICY, } from './import-policy.js';
15
16
  export const DIAGNOSTIC_FAMILIES = [
16
17
  {
17
18
  prefix: 'XE00',
@@ -142,26 +143,61 @@ export const DIAGNOSTIC_DEFINITIONS = [
142
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),
143
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),
144
145
  define('XE1201', 'A source import breaks the zone boundary: it is absolute, leaves the project root, '
145
- + 'reaches the opposite entrypoint, or the module is reachable from both graphs while living outside '
146
- + 'src/shared/.', 'Keep imports relative and inside the project. Move genuinely shared code to src/shared/ and update '
147
- + 'both importers; never import one entrypoint from the other.', CHECKED_BY_EVERY_COMPILE),
148
- define('XE1202', 'A package import is not on the allowlist for that zone.', 'The client zone may import @impetik/xeer/client, /client/core, /shared, the JSX runtimes, and '
149
- + 'preact; the server zone may import @impetik/xeer/server and /shared. Node builtins are never '
150
- + '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),
151
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),
152
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 '
153
168
  + 'candidate extensions rather than rewriting them, so ./shared/title.js does not find '
154
169
  + 'shared/title.ts. Directory imports resolve to index.*.', CHECKED_BY_EVERY_COMPILE),
155
170
  define('XE1205', 'TypeScript reported an error in a file reachable from an entrypoint. The message '
156
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 '
157
- + '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),
158
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),
176
+ define('XE1207', 'An import names an npm package that package.json does not declare in '
177
+ + '"dependencies". It may still resolve through hoisting, which is exactly the accident the rule '
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 '
181
+ + 'devDependencies instead.', CHECKED_BY_EVERY_COMPILE),
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 '
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),
159
190
  define('XE1300', 'The default server export is not a statically inspectable defineServer({...}) call, '
160
191
  + 'or an operation group is not a literal object.', 'Export `default defineServer({ queries: {...}, mutations: {...}, endpoints: {...} })` with literal '
161
192
  + 'object members: no spreads, shorthand, computed keys, or wrappers.', CHECKED_BY_EVERY_COMPILE),
162
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),
163
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),
164
- 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),
165
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: '
166
202
  + 'GET /api/notes/:id and GET /api/notes/:slug are the same route.', CHECKED_BY_EVERY_COMPILE),
167
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),
@@ -169,8 +205,34 @@ export const DIAGNOSTIC_DEFINITIONS = [
169
205
  define('XE1403', 'A file in public/ claims a platform-reserved path.', 'Move it: /_xeer/*, /__xeer/* and /_xa/* belong to the platform.', BUILT_BY_EVERY_BUILD),
170
206
  define('XE1404', 'The manifest declares a favicon the project does not ship in public/.', 'Add the file under public/, or drop app.favicon. Emitting the reference anyway would put a '
171
207
  + 'link that 404s into every document the application serves.', BUILT_BY_EVERY_BUILD),
208
+ define('XE1405', 'The minified client JavaScript bundle exceeds its byte budget — the platform\'s '
209
+ + `${Math.round(CLIENT_BUNDLE_ERROR_BUDGET_BYTES / 1024)} KiB default, or the manifest\'s `
210
+ + '`budgets.clientBundleBytes` when declared. The message attributes bytes to the packages that '
211
+ + 'contributed them.', 'Read the attribution in the message and remove or replace the heaviest dependency, or raise '
212
+ + '`budgets.clientBundleBytes` (bounded by the 10 MiB module ceiling) if the size is intended.', BUILT_BY_EVERY_BUILD),
213
+ define('XE1406', 'The minified client JavaScript bundle exceeds the fixed '
214
+ + `${Math.round(CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES / 1024)} KiB advisory tier. The build still `
215
+ + 'succeeds; this is a warning with per-package byte attribution.', 'Nothing is required. If the growth is unintended, the attribution in the message names the '
216
+ + 'packages to reconsider before the error tier is reached.', BUILT_BY_EVERY_BUILD),
172
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 '
173
218
  + 'support, rather than a Xeer rule.', 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),
227
+ define('XE1503', 'The client bundle would contain a second physical copy of the managed renderer. '
228
+ + 'Bare renderer imports are pinned to the platform anchor, so a second copy means a path-based '
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 '
230
+ + 'it to one instance. Two renderer copies means two options singletons — hooks and components '
231
+ + 'silently stop sharing state.',
232
+ // The analyzer admits path imports against the managed-renderer roots, so the escape is reported
233
+ // at the user's own import during `check`; the build's metafile verification reports the same
234
+ // code for the same defect one stage later, when it survives as a second bundled copy.
235
+ CHECKED_BY_EVERY_COMPILE),
174
236
  define('XE1602', 'A watch rebuild failed. The previously accepted generation is still serving.', 'Fix the edit named by `file`. The next quiet-window rebuild promotes automatically; no restart.', ['dev']),
175
237
  define('XE1603', 'A rebuilt candidate compiled but failed to take over — usually a manifest or schema '
176
238
  + 'change the running state cannot accept — and the last-good generation was restored.', 'Fix the manifest or schema change. To adopt an incompatible schema locally, stop dev and run '
@@ -238,8 +300,10 @@ export const DIAGNOSTIC_DEFINITIONS = [
238
300
  + "names: pass it with as({ name, workspaceIds }).", ['test']),
239
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 '
240
302
  + 'operation that never returns, not a slow machine.', ['test']),
241
- 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, '
242
- + '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']),
243
307
  define('XE2002', 'The inspector request itself failed: a local dev/preview inspector that answered '
244
308
  + 'a non-2xx status or no JSON, or a control plane that refused the proxied read, in which case '
245
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 '
@@ -315,6 +379,11 @@ export const DIAGNOSTIC_DEFINITIONS = [
315
379
  define('XE5133', 'A closed-beta quota refused the environment-variable request: `xeer env set` claims '
316
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 '
317
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']),
318
387
  define('XE5139', 'xeer env failed without a more specific code.', 'Read `message`; it is the underlying error verbatim.', ['env']),
319
388
  define('XE5140', 'An `xeer deployments` invocation is wrong: an unusable --limit, or a directory that '
320
389
  + 'declares no app identity.', 'Read `message`. --limit takes a positive integer up to 200. In a directory with no appId, run '
@@ -437,9 +506,18 @@ export function renderDiagnosticsReference() {
437
506
  ' file?: string; // project-relative POSIX path, never absolute',
438
507
  ' span?: { line: number; column: number; length?: number }; // 1-based',
439
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',
440
511
  '}',
441
512
  '```',
442
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
+ '',
443
521
  'The "Seen in" column lists the commands whose JSON can carry the code. `check` diagnostics also appear',
444
522
  'in `build` and `dev`, because both run the same manifest and analysis stages first.',
445
523
  ];
@@ -477,9 +555,20 @@ export function renderDiagnosticsDocsPage() {
477
555
  ' file?: string; // project-relative POSIX path, never absolute',
478
556
  ' span?: { line: number; column: number; length?: number }; // 1-based',
479
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',
480
560
  '}',
481
561
  '```',
482
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
+ '',
483
572
  'Every command prints these as JSON with `--json`; see [Building with AI agents](/guides/agents).',
484
573
  'The "Seen in" column lists the commands whose output can carry the code. `check` diagnostics also',
485
574
  'appear in `build` and `dev`, because both run the same manifest and analysis stages first.',
@@ -0,0 +1,172 @@
1
+ /**
2
+ * The per-zone import policy: the one hand-maintained statement of what a bare specifier may mean
3
+ * in each zone (#212).
4
+ *
5
+ * Before this module existed the same facts lived in five independently maintained forms — the
6
+ * analyzer's client-package allowlist, the platform type-module resolver, the build-time runtime
7
+ * aliases and their entrypoint-matching regex, the dev server's alias/dedupe/optimizeDeps block,
8
+ * and the diagnostics prose — and issue #210 was the drift that duplication invites: validation
9
+ * accepted an import that bundling then resolved to a second Preact instance. Every one of those
10
+ * consumers now derives from the two policy instances declared here.
11
+ *
12
+ * Deliberately free of I/O and of `require.resolve`: this module states *policy*, and the compiler
13
+ * turns it into resolved file paths against the platform's own installed renderer (its "anchor").
14
+ * Keeping it in the spec package lets the diagnostics catalogue render its guidance from the same
15
+ * source, and vendoring folds it into the published tarball with everything else.
16
+ */
17
+ /** The two module graphs the compiler walks. Test files have their own surface and are not a zone. */
18
+ export type ImportZone = 'client' | 'server';
19
+ /**
20
+ * What one public entrypoint of the managed renderer package is *for*. Admission derives from this:
21
+ * `runtime` and `development` entrypoints may be imported by client application code; `test` and
22
+ * `metadata` entrypoints are deliberately rejected there (XE1202) so a test-only or package-metadata
23
+ * path can never become a production-client import merely because the package publishes it.
24
+ */
25
+ export type RendererEntrypointClassification =
26
+ /** Part of the supported client runtime surface, in any build mode. */
27
+ 'runtime'
28
+ /** Supported, but exists for development tooling; mutates the renderer's shared options. */
29
+ | 'development'
30
+ /** Test-only surface. Never a production-client import. */
31
+ | 'test'
32
+ /** A `package.json` subpath: metadata, not code. Pinned during bundling, rejected as an import. */
33
+ | 'metadata';
34
+ /**
35
+ * A package family whose members must resolve to one physical package root regardless of importer —
36
+ * the renderer, on the client. `entrypoints` classifies every public export of the anchor package;
37
+ * `compatAliases` maps the React-ecosystem specifiers onto their `preact/compat` targets, which is
38
+ * React-library compatibility under the Preact provider, not BYO React (#214 owns a real adapter).
39
+ */
40
+ export interface ManagedPackageFamily {
41
+ /** The one package whose installed copy anchors every member of the family. */
42
+ readonly anchorPackage: string;
43
+ /** Bare package names the family owns; any subpath of these is family territory. */
44
+ readonly packages: readonly string[];
45
+ /** Every public entrypoint of the anchor package, classified. Keys are full specifiers. */
46
+ readonly entrypoints: ReadonlyMap<string, RendererEntrypointClassification>;
47
+ /** Per-subpath React-family aliases onto anchor entrypoints. Keys are full specifiers. */
48
+ readonly compatAliases: ReadonlyMap<string, string>;
49
+ readonly jsx: {
50
+ readonly importSource: string;
51
+ readonly runtime: string;
52
+ readonly devRuntime: string;
53
+ };
54
+ }
55
+ export interface ZoneImportPolicy {
56
+ readonly zone: ImportZone;
57
+ /**
58
+ * `@impetik/xeer/*` subpaths this zone admits. What each one resolves to is the compiler's
59
+ * knowledge — the SDK module targets live beside its resolver, not in this declarative layer.
60
+ */
61
+ readonly platformModules: readonly string[];
62
+ /** Singleton-sensitive families. The renderer on the client; empty on the server. */
63
+ readonly managedFamilies: readonly ManagedPackageFamily[];
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. */
73
+ readonly applicationDependencies: {
74
+ readonly mode: 'closed' | 'open';
75
+ /** Bundler resolution conditions for this zone's application dependencies. */
76
+ readonly conditions: readonly string[];
77
+ /** Compile-time defines applied when bundling this zone. */
78
+ readonly defines: Readonly<Record<string, string>>;
79
+ /** Byte budgets over the zone's single-chunk bundle; `null` when the zone declares none. */
80
+ readonly bundleBudget: {
81
+ readonly errorBytes: number;
82
+ readonly advisoryBytes: number;
83
+ } | null;
84
+ };
85
+ }
86
+ /**
87
+ * The normalized client runtime: the only implemented provider/source combination. `client.runtime`
88
+ * in the manifest is optional and omission normalizes to exactly this value, so declaring it is a
89
+ * statement of the default rather than a choice.
90
+ */
91
+ export declare const NORMALIZED_CLIENT_RUNTIME: Readonly<{
92
+ readonly provider: "preact";
93
+ readonly source: "platform";
94
+ }>;
95
+ /** The normalized client runtime as compiler facts and artifact provenance spell it. */
96
+ export declare const CLIENT_RUNTIME_ID: "preact/platform";
97
+ /**
98
+ * Default and advisory budgets for the open client bundle, minified pre-gzip bytes of the single
99
+ * JavaScript chunk. The error tier is overridable through manifest `budgets.clientBundleBytes`
100
+ * under the existing normalization pattern, bounded by the 10 MiB module ceiling; the advisory tier
101
+ * is fixed. Values are a product decision recorded in #212.
102
+ */
103
+ export declare const CLIENT_BUNDLE_ERROR_BUDGET_BYTES: number;
104
+ export declare const CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES: number;
105
+ /** The client renderer family under the Preact provider. */
106
+ export declare const PREACT_RENDERER_FAMILY: ManagedPackageFamily;
107
+ export declare const CLIENT_IMPORT_POLICY: ZoneImportPolicy;
108
+ /**
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.
125
+ */
126
+ export declare const SERVER_IMPORT_POLICY: ZoneImportPolicy;
127
+ export declare function zoneImportPolicy(zone: ImportZone): ZoneImportPolicy;
128
+ /** The bare package name of a specifier: the first segment, or the first two for a scope. */
129
+ export declare function packageNameOfSpecifier(specifier: string): string;
130
+ /** How a zone's policy answers one authored bare specifier. */
131
+ export type BareImportAdmission =
132
+ /** An `@impetik/xeer/*` subpath this zone admits. */
133
+ {
134
+ kind: 'platform';
135
+ }
136
+ /** A managed-family specifier this zone admits; `target` is the anchor entrypoint it pins to. */
137
+ | {
138
+ kind: 'managed';
139
+ classification: RendererEntrypointClassification;
140
+ target: string;
141
+ }
142
+ /** A managed-family specifier this zone deliberately rejects. */
143
+ | {
144
+ kind: 'managed-rejected';
145
+ reason: 'test-only' | 'metadata' | 'unknown-entrypoint';
146
+ }
147
+ /** An application dependency, admitted subject to the declaration invariant. */
148
+ | {
149
+ kind: 'open';
150
+ }
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
+ */
157
+ | {
158
+ kind: 'rejected';
159
+ reason: 'platform-subpath' | 'foreign-family' | 'closed';
160
+ };
161
+ /**
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.
166
+ */
167
+ export declare function admitBareImport(policy: ZoneImportPolicy, specifier: string): BareImportAdmission;
168
+ /**
169
+ * The admitted managed-family surface of a zone, alias keys included — what bundling and the dev
170
+ * server must pin to the anchor, and what documentation lists as the renderer surface.
171
+ */
172
+ export declare function managedImportSpecifiers(policy: ZoneImportPolicy): ReadonlyMap<string, string>;