@impetik/xeer-mcp 0.2.16 → 0.2.17

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.17",
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.17"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^24.1.0"
@@ -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',
@@ -145,9 +146,14 @@ export const DIAGNOSTIC_DEFINITIONS = [
145
146
  + 'reaches the opposite entrypoint, or the module is reachable from both graphs while living outside '
146
147
  + 'src/shared/.', 'Keep imports relative and inside the project. Move genuinely shared code to src/shared/ and update '
147
148
  + '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),
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),
151
157
  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
158
  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
159
  + 'candidate extensions rather than rewriting them, so ./shared/title.js does not find '
@@ -156,6 +162,14 @@ export const DIAGNOSTIC_DEFINITIONS = [
156
162
  + '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
163
  + 'between a client call and its server handler surfaces here.', CHECKED_BY_EVERY_COMPILE),
158
164
  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 '
166
+ + '"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 '
169
+ + '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 '
172
+ + 'present on disk, then check again.', CHECKED_BY_EVERY_COMPILE),
159
173
  define('XE1300', 'The default server export is not a statically inspectable defineServer({...}) call, '
160
174
  + 'or an operation group is not a literal object.', 'Export `default defineServer({ queries: {...}, mutations: {...}, endpoints: {...} })` with literal '
161
175
  + 'object members: no spreads, shorthand, computed keys, or wrappers.', CHECKED_BY_EVERY_COMPILE),
@@ -169,8 +183,32 @@ export const DIAGNOSTIC_DEFINITIONS = [
169
183
  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
184
  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
185
  + 'link that 404s into every document the application serves.', BUILT_BY_EVERY_BUILD),
186
+ define('XE1405', 'The minified client JavaScript bundle exceeds its byte budget — the platform\'s '
187
+ + `${Math.round(CLIENT_BUNDLE_ERROR_BUDGET_BYTES / 1024)} KiB default, or the manifest\'s `
188
+ + '`budgets.clientBundleBytes` when declared. The message attributes bytes to the packages that '
189
+ + 'contributed them.', 'Read the attribution in the message and remove or replace the heaviest dependency, or raise '
190
+ + '`budgets.clientBundleBytes` (bounded by the 10 MiB module ceiling) if the size is intended.', BUILT_BY_EVERY_BUILD),
191
+ define('XE1406', 'The minified client JavaScript bundle exceeds the fixed '
192
+ + `${Math.round(CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES / 1024)} KiB advisory tier. The build still `
193
+ + '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 '
194
+ + 'packages to reconsider before the error tier is reached.', BUILT_BY_EVERY_BUILD),
172
195
  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
196
  + '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),
203
+ define('XE1503', 'The client bundle would contain a second physical copy of the managed renderer. '
204
+ + 'Bare renderer imports are pinned to the platform anchor, so a second copy means a path-based '
205
+ + '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 '
206
+ + 'it to one instance. Two renderer copies means two options singletons — hooks and components '
207
+ + 'silently stop sharing state.',
208
+ // The analyzer admits path imports against the managed-renderer roots, so the escape is reported
209
+ // at the user's own import during `check`; the build's metafile verification reports the same
210
+ // code for the same defect one stage later, when it survives as a second bundled copy.
211
+ CHECKED_BY_EVERY_COMPILE),
174
212
  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
213
  define('XE1603', 'A rebuilt candidate compiled but failed to take over — usually a manifest or schema '
176
214
  + '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 '
@@ -0,0 +1,145 @@
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 today. */
63
+ readonly managedFamilies: readonly ManagedPackageFamily[];
64
+ /** The open set. `closed` reproduces the pre-#212 behavior exactly. */
65
+ readonly applicationDependencies: {
66
+ readonly mode: 'closed' | 'open';
67
+ /** Bundler resolution conditions for this zone's application dependencies. */
68
+ readonly conditions: readonly string[];
69
+ /** Compile-time defines applied when bundling this zone. */
70
+ readonly defines: Readonly<Record<string, string>>;
71
+ /** Byte budgets over the zone's single-chunk bundle; `null` while the zone is closed. */
72
+ readonly bundleBudget: {
73
+ readonly errorBytes: number;
74
+ readonly advisoryBytes: number;
75
+ } | null;
76
+ };
77
+ }
78
+ /**
79
+ * The normalized client runtime: the only implemented provider/source combination. `client.runtime`
80
+ * in the manifest is optional and omission normalizes to exactly this value, so declaring it is a
81
+ * statement of the default rather than a choice.
82
+ */
83
+ export declare const NORMALIZED_CLIENT_RUNTIME: Readonly<{
84
+ readonly provider: "preact";
85
+ readonly source: "platform";
86
+ }>;
87
+ /** The normalized client runtime as compiler facts and artifact provenance spell it. */
88
+ export declare const CLIENT_RUNTIME_ID: "preact/platform";
89
+ /**
90
+ * Default and advisory budgets for the open client bundle, minified pre-gzip bytes of the single
91
+ * JavaScript chunk. The error tier is overridable through manifest `budgets.clientBundleBytes`
92
+ * under the existing normalization pattern, bounded by the 10 MiB module ceiling; the advisory tier
93
+ * is fixed. Values are a product decision recorded in #212.
94
+ */
95
+ export declare const CLIENT_BUNDLE_ERROR_BUDGET_BYTES: number;
96
+ export declare const CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES: number;
97
+ /** The client renderer family under the Preact provider. */
98
+ export declare const PREACT_RENDERER_FAMILY: ManagedPackageFamily;
99
+ export declare const CLIENT_IMPORT_POLICY: ZoneImportPolicy;
100
+ /**
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.
104
+ */
105
+ export declare const SERVER_IMPORT_POLICY: ZoneImportPolicy;
106
+ export declare function zoneImportPolicy(zone: ImportZone): ZoneImportPolicy;
107
+ /** The bare package name of a specifier: the first segment, or the first two for a scope. */
108
+ export declare function packageNameOfSpecifier(specifier: string): string;
109
+ /** How a zone's policy answers one authored bare specifier. */
110
+ export type BareImportAdmission =
111
+ /** An `@impetik/xeer/*` subpath this zone admits. */
112
+ {
113
+ kind: 'platform';
114
+ }
115
+ /** A managed-family specifier this zone admits; `target` is the anchor entrypoint it pins to. */
116
+ | {
117
+ kind: 'managed';
118
+ classification: RendererEntrypointClassification;
119
+ target: string;
120
+ }
121
+ /** A managed-family specifier this zone deliberately rejects. */
122
+ | {
123
+ kind: 'managed-rejected';
124
+ reason: 'test-only' | 'metadata' | 'unknown-entrypoint';
125
+ }
126
+ /** An application dependency, admitted subject to the declaration invariant. */
127
+ | {
128
+ kind: 'open';
129
+ }
130
+ /** Structurally rejected: a Node builtin, a foreign platform subpath, or a closed zone. */
131
+ | {
132
+ kind: 'rejected';
133
+ reason: 'platform-subpath' | 'closed';
134
+ };
135
+ /**
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.
139
+ */
140
+ export declare function admitBareImport(policy: ZoneImportPolicy, specifier: string): BareImportAdmission;
141
+ /**
142
+ * The admitted managed-family surface of a zone, alias keys included — what bundling and the dev
143
+ * server must pin to the anchor, and what documentation lists as the renderer surface.
144
+ */
145
+ export declare function managedImportSpecifiers(policy: ZoneImportPolicy): ReadonlyMap<string, string>;
@@ -0,0 +1,194 @@
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
+ /**
18
+ * The normalized client runtime: the only implemented provider/source combination. `client.runtime`
19
+ * in the manifest is optional and omission normalizes to exactly this value, so declaring it is a
20
+ * statement of the default rather than a choice.
21
+ */
22
+ export const NORMALIZED_CLIENT_RUNTIME = Object.freeze({
23
+ provider: 'preact',
24
+ source: 'platform',
25
+ });
26
+ /** The normalized client runtime as compiler facts and artifact provenance spell it. */
27
+ export const CLIENT_RUNTIME_ID = `${NORMALIZED_CLIENT_RUNTIME.provider}/${NORMALIZED_CLIENT_RUNTIME.source}`;
28
+ /**
29
+ * Default and advisory budgets for the open client bundle, minified pre-gzip bytes of the single
30
+ * JavaScript chunk. The error tier is overridable through manifest `budgets.clientBundleBytes`
31
+ * under the existing normalization pattern, bounded by the 10 MiB module ceiling; the advisory tier
32
+ * is fixed. Values are a product decision recorded in #212.
33
+ */
34
+ export const CLIENT_BUNDLE_ERROR_BUDGET_BYTES = 1024 * 1024;
35
+ export const CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES = 400 * 1024;
36
+ /**
37
+ * Every public entrypoint of the platform Preact package, classified. The compiler's conformance
38
+ * test compares this map against the installed package's `exports` map in both directions, so a
39
+ * Preact upgrade that adds, removes, or renames an entrypoint fails until it is deliberately
40
+ * classified here.
41
+ */
42
+ const PREACT_ENTRYPOINTS = new Map([
43
+ ['preact', 'runtime'],
44
+ ['preact/hooks', 'runtime'],
45
+ ['preact/jsx-runtime', 'runtime'],
46
+ ['preact/jsx-dev-runtime', 'runtime'],
47
+ ['preact/compat', 'runtime'],
48
+ ['preact/compat/client', 'runtime'],
49
+ // Browser-safe server rendering: both resolve to the browser build under the client conditions.
50
+ ['preact/compat/server', 'runtime'],
51
+ ['preact/compat/server.browser', 'runtime'],
52
+ ['preact/compat/jsx-runtime', 'runtime'],
53
+ ['preact/compat/jsx-dev-runtime', 'runtime'],
54
+ ['preact/compat/scheduler', 'runtime'],
55
+ // Side-effectful development surfaces: they mutate the shared renderer options, which is exactly
56
+ // why they must be pinned to the same singleton as everything else.
57
+ ['preact/debug', 'development'],
58
+ ['preact/devtools', 'development'],
59
+ ['preact/test-utils', 'test'],
60
+ ['preact/compat/test-utils', 'test'],
61
+ ['preact/package.json', 'metadata'],
62
+ ['preact/compat/package.json', 'metadata'],
63
+ ['preact/debug/package.json', 'metadata'],
64
+ ['preact/devtools/package.json', 'metadata'],
65
+ ['preact/hooks/package.json', 'metadata'],
66
+ ['preact/test-utils/package.json', 'metadata'],
67
+ ['preact/jsx-runtime/package.json', 'metadata'],
68
+ ]);
69
+ /**
70
+ * The complete per-subpath React-family alias table. Every key is pinned to its target for every
71
+ * importer — application code, SDK, JSX runtime, generated bootstrap, and bundled dependencies'
72
+ * own imports alike — so a React-ecosystem library and the platform renderer can never diverge
73
+ * into two instances. The installed Preact publishes a matching export for every target.
74
+ */
75
+ const REACT_COMPAT_ALIASES = new Map([
76
+ ['react', 'preact/compat'],
77
+ ['react/jsx-runtime', 'preact/compat/jsx-runtime'],
78
+ ['react/jsx-dev-runtime', 'preact/compat/jsx-dev-runtime'],
79
+ ['react-dom', 'preact/compat'],
80
+ ['react-dom/client', 'preact/compat/client'],
81
+ ['react-dom/server', 'preact/compat/server'],
82
+ ['react-dom/server.browser', 'preact/compat/server.browser'],
83
+ ['react-dom/test-utils', 'preact/compat/test-utils'],
84
+ ['scheduler', 'preact/compat/scheduler'],
85
+ ]);
86
+ /** The client renderer family under the Preact provider. */
87
+ export const PREACT_RENDERER_FAMILY = Object.freeze({
88
+ anchorPackage: 'preact',
89
+ packages: Object.freeze(['preact', 'react', 'react-dom', 'scheduler']),
90
+ entrypoints: PREACT_ENTRYPOINTS,
91
+ compatAliases: REACT_COMPAT_ALIASES,
92
+ jsx: Object.freeze({
93
+ importSource: '@impetik/xeer',
94
+ runtime: 'preact/jsx-runtime',
95
+ devRuntime: 'preact/jsx-dev-runtime',
96
+ }),
97
+ });
98
+ const CLIENT_PLATFORM_MODULES = Object.freeze([
99
+ '@impetik/xeer/client',
100
+ '@impetik/xeer/client/core',
101
+ '@impetik/xeer/shared',
102
+ '@impetik/xeer/jsx-runtime',
103
+ '@impetik/xeer/jsx-dev-runtime',
104
+ ]);
105
+ const SERVER_PLATFORM_MODULES = Object.freeze([
106
+ '@impetik/xeer/server',
107
+ '@impetik/xeer/shared',
108
+ ]);
109
+ export const CLIENT_IMPORT_POLICY = Object.freeze({
110
+ zone: 'client',
111
+ platformModules: CLIENT_PLATFORM_MODULES,
112
+ managedFamilies: Object.freeze([PREACT_RENDERER_FAMILY]),
113
+ applicationDependencies: Object.freeze({
114
+ mode: 'open',
115
+ conditions: Object.freeze(['browser', 'import']),
116
+ defines: Object.freeze({ 'process.env.NODE_ENV': '"production"' }),
117
+ bundleBudget: Object.freeze({
118
+ errorBytes: CLIENT_BUNDLE_ERROR_BUDGET_BYTES,
119
+ advisoryBytes: CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES,
120
+ }),
121
+ }),
122
+ });
123
+ /**
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.
127
+ */
128
+ export const SERVER_IMPORT_POLICY = Object.freeze({
129
+ zone: 'server',
130
+ platformModules: SERVER_PLATFORM_MODULES,
131
+ managedFamilies: Object.freeze([]),
132
+ applicationDependencies: Object.freeze({
133
+ mode: 'closed',
134
+ conditions: Object.freeze([]),
135
+ defines: Object.freeze({}),
136
+ bundleBudget: null,
137
+ }),
138
+ });
139
+ export function zoneImportPolicy(zone) {
140
+ return zone === 'client' ? CLIENT_IMPORT_POLICY : SERVER_IMPORT_POLICY;
141
+ }
142
+ /** The bare package name of a specifier: the first segment, or the first two for a scope. */
143
+ export function packageNameOfSpecifier(specifier) {
144
+ const segments = specifier.split('/');
145
+ return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
146
+ }
147
+ /**
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.
151
+ */
152
+ export function admitBareImport(policy, specifier) {
153
+ if (policy.platformModules.includes(specifier))
154
+ return { kind: 'platform' };
155
+ if (specifier === '@impetik/xeer' || specifier.startsWith('@impetik/xeer/')) {
156
+ return { kind: 'rejected', reason: 'platform-subpath' };
157
+ }
158
+ const packageName = packageNameOfSpecifier(specifier);
159
+ for (const family of policy.managedFamilies) {
160
+ if (!family.packages.includes(packageName))
161
+ continue;
162
+ const target = family.compatAliases.get(specifier) ?? specifier;
163
+ const classification = family.entrypoints.get(target);
164
+ if (!classification)
165
+ return { kind: 'managed-rejected', reason: 'unknown-entrypoint' };
166
+ if (classification === 'test')
167
+ return { kind: 'managed-rejected', reason: 'test-only' };
168
+ if (classification === 'metadata')
169
+ return { kind: 'managed-rejected', reason: 'metadata' };
170
+ return { kind: 'managed', classification, target };
171
+ }
172
+ if (policy.applicationDependencies.mode === 'open')
173
+ return { kind: 'open' };
174
+ return { kind: 'rejected', reason: 'closed' };
175
+ }
176
+ /**
177
+ * The admitted managed-family surface of a zone, alias keys included — what bundling and the dev
178
+ * server must pin to the anchor, and what documentation lists as the renderer surface.
179
+ */
180
+ export function managedImportSpecifiers(policy) {
181
+ const result = new Map();
182
+ for (const family of policy.managedFamilies) {
183
+ for (const [specifier, classification] of family.entrypoints) {
184
+ if (classification === 'runtime' || classification === 'development')
185
+ result.set(specifier, specifier);
186
+ }
187
+ for (const [alias, target] of family.compatAliases) {
188
+ const classification = family.entrypoints.get(target);
189
+ if (classification === 'runtime' || classification === 'development')
190
+ result.set(alias, target);
191
+ }
192
+ }
193
+ return result;
194
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './types.js';
2
2
  export * from './diagnostics.js';
3
+ export * from './import-policy.js';
3
4
  export * from './schema.js';
4
5
  export * from './storage.js';
5
6
  export * from './canonical.js';
@@ -1,5 +1,6 @@
1
1
  export * from './types.js';
2
2
  export * from './diagnostics.js';
3
+ export * from './import-policy.js';
3
4
  export * from './schema.js';
4
5
  export * from './storage.js';
5
6
  export * from './canonical.js';
@@ -43,11 +43,12 @@ export declare const IMMUTABLE_ASSET_HASH_LENGTH = 64;
43
43
  /**
44
44
  * Upper bound on {@link PublicAssetsV0.retainedGenerations}.
45
45
  *
46
- * One, because one is what the control plane implements. A wider range was declared first and that
46
+ * Two, because two is what the control plane implements. A wider range was declared first and that
47
47
  * was a promise the code did not keep: an artifact could ask for four generations and silently get
48
- * one. A contract that can express a value nothing honours is worse than a narrow contract.
48
+ * one. A contract that can express a value nothing honours is worse than a narrow contract, so this
49
+ * bound moves only in lockstep with the retention fold in the control plane's deployment service.
49
50
  */
50
- export declare const MAX_RETAINED_ASSET_GENERATIONS = 1;
51
+ export declare const MAX_RETAINED_ASSET_GENERATIONS = 2;
51
52
  /**
52
53
  * What a declared entry is an alias *of*. Kept as a string rather than a structured pair because its
53
54
  * only consumers are humans reading an artifact and a diagnostic naming the file that produced a
@@ -95,9 +96,11 @@ export interface PublicAssetsV0 {
95
96
  /**
96
97
  * How many *previous* deployments' immutable assets keep serving alongside this one.
97
98
  *
98
- * The value is `0` or `1` and nothing else — see {@link MAX_RETAINED_ASSET_GENERATIONS}. `1` means
99
- * the deployment this artifact replaces keeps answering for its content-addressed paths; `0` means
100
- * it does not, and a client holding the older document gets a 404.
99
+ * The value is `0`, `1`, or `2` and nothing else — see {@link MAX_RETAINED_ASSET_GENERATIONS}.
100
+ * `1` means the deployment this artifact replaces keeps answering for its content-addressed paths;
101
+ * `2` extends that to the deployment before it, which is what an artifact carrying package-derived
102
+ * subresources asks for so a document cached across two deploys still resolves its assets; `0`
103
+ * means nothing is retained, and a client holding an older document gets a 404.
101
104
  *
102
105
  * Worth recording honestly: this is the *incoming* artifact selecting an obligation that is owed to
103
106
  * documents emitted by the *outgoing* one, and deployment cadence is a control-plane concern rather
@@ -43,11 +43,12 @@ export const IMMUTABLE_ASSET_HASH_LENGTH = 64;
43
43
  /**
44
44
  * Upper bound on {@link PublicAssetsV0.retainedGenerations}.
45
45
  *
46
- * One, because one is what the control plane implements. A wider range was declared first and that
46
+ * Two, because two is what the control plane implements. A wider range was declared first and that
47
47
  * was a promise the code did not keep: an artifact could ask for four generations and silently get
48
- * one. A contract that can express a value nothing honours is worse than a narrow contract.
48
+ * one. A contract that can express a value nothing honours is worse than a narrow contract, so this
49
+ * bound moves only in lockstep with the retention fold in the control plane's deployment service.
49
50
  */
50
- export const MAX_RETAINED_ASSET_GENERATIONS = 1;
51
+ export const MAX_RETAINED_ASSET_GENERATIONS = 2;
51
52
  const IMMUTABLE_PATH = /^\/_xa\/[0-9a-f]{64}\/[A-Za-z0-9](?:[A-Za-z0-9._-]{0,63})$/u;
52
53
  const ASSET_HASH = /^sha256:[0-9a-f]{64}$/u;
53
54
  /** The URL a blob of bytes is served at, and the only sanctioned way to mint one. */
@@ -151,11 +151,18 @@ export declare const applicationManifestSchema: z.ZodObject<{
151
151
  database: "database";
152
152
  storage: "storage";
153
153
  }>>>;
154
+ client: z.ZodOptional<z.ZodObject<{
155
+ runtime: z.ZodOptional<z.ZodObject<{
156
+ provider: z.ZodLiteral<"preact">;
157
+ source: z.ZodOptional<z.ZodLiteral<"platform">>;
158
+ }, z.core.$strict>>;
159
+ }, z.core.$strict>>;
154
160
  budgets: z.ZodOptional<z.ZodObject<{
155
161
  queryRows: z.ZodOptional<z.ZodNumber>;
156
162
  mutationWrites: z.ZodOptional<z.ZodNumber>;
157
163
  requestBytes: z.ZodOptional<z.ZodNumber>;
158
164
  responseBytes: z.ZodOptional<z.ZodNumber>;
165
+ clientBundleBytes: z.ZodOptional<z.ZodNumber>;
159
166
  liveConnections: z.ZodOptional<z.ZodNumber>;
160
167
  }, z.core.$strict>>;
161
168
  }, z.core.$strict>;
@@ -271,11 +271,26 @@ export const applicationManifestSchema = z.strictObject({
271
271
  writeBytes: z.number().int().positive().max(STORAGE_MAX_WRITE_BYTES).optional(),
272
272
  }).optional(),
273
273
  capabilities: z.array(z.enum(CAPABILITIES)).optional(),
274
+ /**
275
+ * The client runtime selection (#212). Closed: only implemented provider/source combinations are
276
+ * accepted, and omitting any level of it normalizes to the platform-managed Preact runtime with
277
+ * identical semantics and build output.
278
+ */
279
+ client: z.strictObject({
280
+ runtime: z.strictObject({
281
+ provider: z.literal('preact'),
282
+ source: z.literal('platform').optional(),
283
+ }).optional(),
284
+ }).optional(),
274
285
  budgets: z.strictObject({
275
286
  queryRows: z.number().int().positive().max(10_000).optional(),
276
287
  mutationWrites: z.number().int().positive().max(1_000).optional(),
277
288
  requestBytes: z.number().int().positive().max(4 * 1024 * 1024).optional(),
278
289
  responseBytes: z.number().int().positive().max(4 * 1024 * 1024).optional(),
290
+ // The error tier for the minified pre-gzip client bundle, defaulting to the platform's 1 MiB.
291
+ // Bounded by the same 10 MiB ceiling every compiled module already has, so raising it can never
292
+ // promise bytes the artifact format refuses.
293
+ clientBundleBytes: z.number().int().positive().max(10 * 1024 * 1024).optional(),
279
294
  // Zero is a declaration, not an omission: it says the application wants no server push at all,
280
295
  // and the runtime answers `live_disabled` for it rather than a retryable budget refusal. The
281
296
  // bound is `min(0)` and not `positive()` so the editor schema publishes `minimum: 0`.
@@ -336,6 +351,7 @@ export const applicationManifestJsonSchema = Object.freeze({
336
351
  app: describeProperty('app', 'Browser metadata and SPA behavior.'),
337
352
  database: describeProperty('database', 'Typed application database schema. Requires the database capability.'),
338
353
  storage: describeProperty('storage', 'Private object-storage limits. Requires the storage capability.'),
354
+ client: describeProperty('client', 'Client runtime selection. Optional; omission means the platform-managed Preact runtime.'),
339
355
  capabilities: {
340
356
  ...describeProperty('capabilities', 'Powers granted to the application. Duplicate names are invalid.'),
341
357
  uniqueItems: true,
@@ -395,6 +411,9 @@ export function normalizeApplicationManifest(manifest) {
395
411
  }
396
412
  : null,
397
413
  capabilities: [...(manifest.capabilities ?? [])].sort(),
414
+ // Always normalized: omission and the explicit default are the same statement, and everything
415
+ // downstream (compiler facts, artifact provenance) reads one canonical value.
416
+ client: { runtime: { provider: 'preact', source: 'platform' } },
398
417
  budgets: {
399
418
  queryRows: manifest.budgets?.queryRows ?? 1_000,
400
419
  mutationWrites: manifest.budgets?.mutationWrites ?? 100,
@@ -404,6 +423,11 @@ export function normalizeApplicationManifest(manifest) {
404
423
  // application's Durable Object continuously, so a positive default charged every application
405
424
  // for a feature most never used. Declare a positive value to turn it on.
406
425
  liveConnections: manifest.budgets?.liveConnections ?? 0,
426
+ // Present only when declared, mirroring `storage`: the platform default is applied at build
427
+ // time, so an application that never declares the budget hashes exactly as before (#212).
428
+ ...(manifest.budgets?.clientBundleBytes !== undefined
429
+ ? { clientBundleBytes: manifest.budgets.clientBundleBytes }
430
+ : {}),
407
431
  },
408
432
  };
409
433
  }
@@ -141,6 +141,18 @@ export interface ApplicationManifestV0 {
141
141
  };
142
142
  storage?: StorageConfigV0;
143
143
  capabilities?: Capability[];
144
+ /**
145
+ * The client runtime selection (#212). Optional, and closed: only implemented provider/source
146
+ * combinations are accepted, and omission normalizes to `{ provider: "preact", source:
147
+ * "platform" }` with identical semantics and build output. Declaring it is a statement of the
148
+ * default, not a choice — a future app-owned source would be `"application"`, not `"user"`.
149
+ */
150
+ client?: {
151
+ runtime?: {
152
+ provider: 'preact';
153
+ source?: 'platform';
154
+ };
155
+ };
144
156
  budgets?: {
145
157
  queryRows?: number;
146
158
  mutationWrites?: number;
@@ -152,6 +164,12 @@ export interface ApplicationManifestV0 {
152
164
  * runtime answers a terminal `live_disabled` without waking the state object.
153
165
  */
154
166
  liveConnections?: number;
167
+ /**
168
+ * Error-tier byte budget for the minified pre-gzip client JavaScript bundle. Defaults to the
169
+ * platform's 1 MiB when omitted; bounded above by the 10 MiB module ceiling. The advisory tier
170
+ * is fixed and not declarable.
171
+ */
172
+ clientBundleBytes?: number;
155
173
  };
156
174
  }
157
175
  export interface NormalizedApplicationManifestV0 {
@@ -176,6 +194,13 @@ export interface NormalizedApplicationManifestV0 {
176
194
  */
177
195
  storage: NormalizedStorageConfigV0 | null;
178
196
  capabilities: Capability[];
197
+ /** Always present once normalized: omission and the explicit default are the same statement. */
198
+ client: {
199
+ runtime: {
200
+ provider: 'preact';
201
+ source: 'platform';
202
+ };
203
+ };
179
204
  budgets: {
180
205
  queryRows: number;
181
206
  mutationWrites: number;
@@ -183,6 +208,11 @@ export interface NormalizedApplicationManifestV0 {
183
208
  responseBytes: number;
184
209
  /** `0` means server push is off for this application; see {@link ApplicationManifestV0}. */
185
210
  liveConnections: number;
211
+ /**
212
+ * Present only when the manifest declares it, mirroring `storage`: an application on the
213
+ * platform default hashes exactly as it did before the budget was declarable.
214
+ */
215
+ clientBundleBytes?: number;
186
216
  };
187
217
  }
188
218
  export interface SourceReceipt {
@@ -190,6 +220,31 @@ export interface SourceReceipt {
190
220
  size: number;
191
221
  hash: `sha256:${string}`;
192
222
  }
223
+ /**
224
+ * Provenance for one npm package the client bundle consumed (#212). `files` receipts the exact
225
+ * bytes the bundler read — strictly stronger than lockfile integrity, which pins tarballs rather
226
+ * than the post-install state a patch or postinstall script left on disk.
227
+ */
228
+ export interface BundledDependencyReceiptV0 {
229
+ name: string;
230
+ version: string;
231
+ /** The metafile inputs consumed from this package, package-root-relative, content-hashed. */
232
+ files: SourceReceipt[];
233
+ }
234
+ /**
235
+ * The application-dependency provenance block, present in an artifact only when the bundle
236
+ * consumed open dependencies. The lockfile identity is opportunistic — recorded when a parseable
237
+ * npm or pnpm lockfile is found — and never an admission gate: workspace applications have no
238
+ * per-app lockfile at all.
239
+ */
240
+ export interface BundledDependenciesV0 {
241
+ packages: BundledDependencyReceiptV0[];
242
+ lockfile?: {
243
+ manager: 'npm' | 'pnpm';
244
+ path: string;
245
+ hash: `sha256:${string}`;
246
+ };
247
+ }
193
248
  export interface ApplicationSchemaIdentityV0 {
194
249
  applicationSchemaVersion: number;
195
250
  schemaHash: `sha256:${string}`;
@@ -222,6 +277,12 @@ export interface ApplicationArtifactV0 {
222
277
  * artifact binds different buckets in preview and production, which is correct (#51 §7).
223
278
  */
224
279
  storage?: NormalizedStorageConfigV0;
280
+ /**
281
+ * The normalized client runtime, recorded when the application declares `client` or the bundle
282
+ * consumed open dependencies. Present-only-then for the same reason as `storage`: an untouched
283
+ * application keeps its `artifactId` (#212).
284
+ */
285
+ clientRuntime?: NormalizedApplicationManifestV0['client']['runtime'];
225
286
  budgets: NormalizedApplicationManifestV0['budgets'];
226
287
  schema: NormalizedApplicationManifestV0['database'];
227
288
  schemaIdentity: ApplicationSchemaIdentityV0;
@@ -264,6 +325,8 @@ export interface ApplicationArtifactV0 {
264
325
  publicAssets: PublicAssetsV0;
265
326
  source: {
266
327
  files: SourceReceipt[];
328
+ /** Present only when the client bundle consumed open application dependencies (#212). */
329
+ dependencies?: BundledDependenciesV0;
267
330
  };
268
331
  }
269
332
  export interface DevEvent<T = unknown> {