@treasuryspatial/plugin-manifest 0.1.9 → 0.1.13

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/dist/index.js CHANGED
@@ -1,3 +1,438 @@
1
+ export const CANONICAL_STARTUP_LIGHTING_BASELINE_ID = 'architectural';
2
+ export const CANONICAL_STARTUP_MATERIALS_BASELINE_ID = 'architectural-white';
3
+ export const CANONICAL_STARTUP_HDR_BASELINE_ID = 'preset';
4
+ const trimModeId = (value) => (typeof value === 'string' ? value.trim() : '');
5
+ const normalizeModeIds = (value) => {
6
+ if (!Array.isArray(value))
7
+ return [];
8
+ const seen = new Set();
9
+ const output = [];
10
+ for (const entry of value) {
11
+ const normalized = trimModeId(entry);
12
+ if (!normalized || seen.has(normalized))
13
+ continue;
14
+ seen.add(normalized);
15
+ output.push(normalized);
16
+ }
17
+ return output;
18
+ };
19
+ const buildCanonicalPromptPackFallbackRef = () => ({
20
+ id: 'vanilla',
21
+ });
22
+ const PLUGIN_MANIFEST_REMOVED_CORE_MATERIAL_SLOTS = ['walls', 'floor', 'ceiling'];
23
+ export class MaterialBaselineContractError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'MaterialBaselineContractError';
27
+ }
28
+ }
29
+ const normalizeSurfaceContract = (value) => {
30
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
31
+ return { surfaces: [] };
32
+ }
33
+ const rawSurfaces = Array.isArray(value.surfaces)
34
+ ? value.surfaces
35
+ : [];
36
+ const seen = new Set();
37
+ const surfaces = [];
38
+ for (const rawSurface of rawSurfaces) {
39
+ if (!rawSurface || typeof rawSurface !== 'object' || Array.isArray(rawSurface))
40
+ continue;
41
+ const id = typeof rawSurface.id === 'string' ? rawSurface.id.trim() : '';
42
+ if (!id)
43
+ continue;
44
+ const normalized = id.toLowerCase();
45
+ if (seen.has(normalized))
46
+ continue;
47
+ seen.add(normalized);
48
+ surfaces.push({
49
+ id,
50
+ label: typeof rawSurface.label === 'string' &&
51
+ rawSurface.label.trim().length > 0
52
+ ? rawSurface.label.trim()
53
+ : id,
54
+ ...(typeof rawSurface.summary === 'string' &&
55
+ rawSurface.summary.trim().length > 0
56
+ ? { summary: rawSurface.summary.trim() }
57
+ : {}),
58
+ ...(typeof rawSurface.parentId === 'string' &&
59
+ rawSurface.parentId.trim().length > 0
60
+ ? { parentId: rawSurface.parentId.trim() }
61
+ : {}),
62
+ });
63
+ }
64
+ return { surfaces };
65
+ };
66
+ const validateMaterialBaselinesAgainstSurfaceContract = (toolId, baselines, surfaceContract) => {
67
+ const declaredSurfaceIds = new Set(surfaceContract.surfaces.map((surface) => surface.id.trim().toLowerCase()));
68
+ for (const baseline of baselines) {
69
+ const materials = baseline.materials;
70
+ if (materials && Object.prototype.hasOwnProperty.call(materials, 'slots')) {
71
+ throw new MaterialBaselineContractError(`tool '${toolId}' materials baseline '${baseline.id}' declares removed materials.slots. Bind declared Geometry surfaces through materials.surfaces.<surfaceId>.`);
72
+ }
73
+ const removedCoreSlots = PLUGIN_MANIFEST_REMOVED_CORE_MATERIAL_SLOTS.filter((slot) => materials ? Object.prototype.hasOwnProperty.call(materials, slot) : false);
74
+ if (removedCoreSlots.length > 0) {
75
+ throw new MaterialBaselineContractError(`tool '${toolId}' materials baseline '${baseline.id}' declares removed core material slots: ${removedCoreSlots
76
+ .map((slot) => `materials.${slot}`)
77
+ .join(', ')}. Plugin manifests must bind declared Geometry surface ids through materials.surfaces.<surfaceId>; Composer core roles are upload/GH-only.`);
78
+ }
79
+ const surfaceBindings = materials && isRecord(materials.surfaces) ? materials.surfaces : {};
80
+ if (Object.keys(surfaceBindings).length === 0) {
81
+ throw new MaterialBaselineContractError(`tool '${toolId}' materials baseline '${baseline.id}' declares no materials.surfaces bindings. Plugin material baselines must bind declared Geometry surface ids; use Composer core material presets for whole-model startup defaults.`);
82
+ }
83
+ const unknownSurfaceIds = Object.keys(surfaceBindings).filter((surfaceId) => !declaredSurfaceIds.has(surfaceId.trim().toLowerCase()));
84
+ if (unknownSurfaceIds.length > 0) {
85
+ throw new MaterialBaselineContractError(`tool '${toolId}' materials baseline '${baseline.id}' binds undeclared surface ids: ${unknownSurfaceIds.join(', ')}. Publish every material target in top-level surfaceContract.surfaces[]; parentId is taxonomy metadata only and is not a material target.`);
86
+ }
87
+ }
88
+ };
89
+ const buildStartupDefaultWarning = (options) => {
90
+ const labelByField = {
91
+ 'baselines.defaults.lightingId': 'lighting',
92
+ 'baselines.defaults.materialsId': 'materials',
93
+ 'baselines.defaults.hdrId': 'HDR',
94
+ };
95
+ const codeByField = {
96
+ 'baselines.defaults.lightingId': 'manifest_startup_lighting_noncanonical',
97
+ 'baselines.defaults.materialsId': 'manifest_startup_materials_noncanonical',
98
+ 'baselines.defaults.hdrId': 'manifest_startup_hdr_noncanonical',
99
+ };
100
+ const actualValue = trimModeId(options.actualValue) || null;
101
+ const fieldLabel = labelByField[options.field];
102
+ return {
103
+ code: codeByField[options.field],
104
+ severity: 'warning',
105
+ field: options.field,
106
+ expectedValue: options.expectedValue,
107
+ actualValue,
108
+ runtimeValue: options.expectedValue,
109
+ message: actualValue
110
+ ? `tool '${options.toolId}' declares non-canonical ${fieldLabel} startup default '${actualValue}'. Composer starts with '${options.expectedValue}' until the manifest is fixed.`
111
+ : `tool '${options.toolId}' is missing canonical ${fieldLabel} startup default '${options.expectedValue}'. Composer starts with '${options.expectedValue}' until the manifest is fixed.`,
112
+ fix: `Set ${options.field} = '${options.expectedValue}' in the manifest.`,
113
+ };
114
+ };
115
+ const resolveStartupDefaultWarnings = (tool, baselines) => {
116
+ const defaults = baselines.defaults ?? {};
117
+ const warnings = [];
118
+ const expectedPairs = [
119
+ ['baselines.defaults.lightingId', defaults.lightingId, CANONICAL_STARTUP_LIGHTING_BASELINE_ID],
120
+ ['baselines.defaults.materialsId', defaults.materialsId, CANONICAL_STARTUP_MATERIALS_BASELINE_ID],
121
+ ['baselines.defaults.hdrId', defaults.hdrId, CANONICAL_STARTUP_HDR_BASELINE_ID],
122
+ ];
123
+ for (const [field, actualValue, expectedValue] of expectedPairs) {
124
+ if (trimModeId(actualValue) === expectedValue)
125
+ continue;
126
+ warnings.push(buildStartupDefaultWarning({
127
+ toolId: tool.toolId,
128
+ field,
129
+ expectedValue,
130
+ actualValue: trimModeId(actualValue) || null,
131
+ }));
132
+ }
133
+ return warnings;
134
+ };
135
+ function resolveToolPromptPack(tool) {
136
+ const allowModes = normalizeModeIds(tool.modeAccess?.allowModes);
137
+ const denyModes = new Set(normalizeModeIds(tool.modeAccess?.denyModes));
138
+ for (const modeId of allowModes) {
139
+ if (denyModes.has(modeId)) {
140
+ throw new Error(`tool '${tool.toolId}' declares conflicting modeAccess rules for '${modeId}'. Remove the overlap between allowModes and denyModes.`);
141
+ }
142
+ }
143
+ const explicitPromptPack = tool.promptPack;
144
+ if (!explicitPromptPack) {
145
+ return buildCanonicalPromptPackFallbackRef();
146
+ }
147
+ const refId = trimModeId(explicitPromptPack.id);
148
+ if (!refId) {
149
+ throw new Error(`tool '${tool.toolId}' declares promptPack without an id. Provide one stable prompt-pack id or remove the field to use the canonical Spatial Render fallback.`);
150
+ }
151
+ if (allowModes.length === 0) {
152
+ throw new Error(`tool '${tool.toolId}' declares promptPack '${refId}' but has no explicit modeAccess.allowModes. Declare the modes this prompt kit is meant to hydrate.`);
153
+ }
154
+ const version = trimModeId(explicitPromptPack.version);
155
+ return {
156
+ id: refId,
157
+ ...(version ? { version } : {}),
158
+ };
159
+ }
160
+ const DEFAULT_OBJECT3D_PRESENTATION = {
161
+ kind: 'object3d',
162
+ defaultViewMode: 'auto',
163
+ units: {
164
+ canonicalScene: 'm',
165
+ },
166
+ camera: {
167
+ kind: 'perspective',
168
+ framingMode: 'iso',
169
+ },
170
+ normalization: {
171
+ recenter: true,
172
+ floorToZero: true,
173
+ allowAutoScale: false,
174
+ },
175
+ ground: {
176
+ mode: 'auto',
177
+ snapToBounds: false,
178
+ },
179
+ grid: {
180
+ mode: 'auto',
181
+ },
182
+ curves: {
183
+ mode: 'auto',
184
+ },
185
+ clipping: {
186
+ nearSafety: 1.6,
187
+ farSafety: 1.15,
188
+ minimumFar: 350,
189
+ },
190
+ };
191
+ const DEFAULT_PLAN2D_PRESENTATION = {
192
+ kind: 'plan2d',
193
+ defaultViewMode: '2d',
194
+ units: {
195
+ canonicalScene: 'm',
196
+ },
197
+ camera: {
198
+ kind: 'orthographic',
199
+ framingMode: 'iso',
200
+ },
201
+ normalization: {
202
+ recenter: true,
203
+ floorToZero: true,
204
+ allowAutoScale: false,
205
+ },
206
+ ground: {
207
+ mode: 'auto',
208
+ snapToBounds: true,
209
+ },
210
+ grid: {
211
+ mode: 'auto',
212
+ },
213
+ curves: {
214
+ mode: 'always',
215
+ },
216
+ clipping: {
217
+ nearSafety: 1.8,
218
+ farSafety: 1.2,
219
+ minimumFar: 350,
220
+ },
221
+ };
222
+ const DEFAULT_TOOL_EXECUTION = {
223
+ timeoutMs: 20_000,
224
+ };
225
+ const hasLegacyPromptingDefinition = (value) => Boolean(value?.packId ||
226
+ value?.packVersion);
227
+ const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
228
+ const mergeDeep = (base, override) => {
229
+ const result = { ...base };
230
+ Object.entries(override).forEach(([key, value]) => {
231
+ const current = result[key];
232
+ if (isRecord(current) && isRecord(value)) {
233
+ result[key] = mergeDeep(current, value);
234
+ return;
235
+ }
236
+ result[key] = value;
237
+ });
238
+ return result;
239
+ };
240
+ const normalizeExecution = (execution) => {
241
+ if (!execution || typeof execution !== 'object')
242
+ return {};
243
+ const timeoutMs = Number(execution.timeoutMs);
244
+ return Number.isFinite(timeoutMs) && timeoutMs > 0
245
+ ? { timeoutMs: Math.round(timeoutMs) }
246
+ : {};
247
+ };
248
+ function resolveToolExecution(tool, plugin) {
249
+ const canonical = normalizeExecution(tool.execution ?? plugin.execution);
250
+ if (canonical.timeoutMs) {
251
+ return {
252
+ execution: {
253
+ ...DEFAULT_TOOL_EXECUTION,
254
+ ...canonical,
255
+ },
256
+ source: 'canonical',
257
+ };
258
+ }
259
+ return {
260
+ execution: DEFAULT_TOOL_EXECUTION,
261
+ source: 'default',
262
+ };
263
+ }
264
+ export const mergeMapInputs = (base, override) => {
265
+ if (Array.isArray(override))
266
+ return override;
267
+ if (Array.isArray(base))
268
+ return base;
269
+ return [];
270
+ };
271
+ const resolveToolUi = (tool, plugin) => {
272
+ if (plugin.ui && isRecord(plugin.ui)) {
273
+ return { ui: plugin.ui, source: 'manifest-ui' };
274
+ }
275
+ const schemaUi = plugin.schemas?.ui;
276
+ if (schemaUi && isRecord(schemaUi)) {
277
+ throw new Error(`tool '${tool.toolId}' declares removed schemas.ui. Move UI to top-level manifest.ui.`);
278
+ }
279
+ throw new Error(`tool '${tool.toolId}' is missing top-level manifest.ui. Composer no longer synthesizes controls from schema shape or legacy tool.panels.`);
280
+ };
281
+ export function resolveToolPresentation(manifest) {
282
+ if (manifest?.presentation) {
283
+ const canonical = manifest.presentation;
284
+ const template = canonical.kind === 'plan2d' ? DEFAULT_PLAN2D_PRESENTATION : DEFAULT_OBJECT3D_PRESENTATION;
285
+ return {
286
+ presentation: mergeDeep(template, canonical),
287
+ source: 'canonical',
288
+ };
289
+ }
290
+ return {
291
+ presentation: DEFAULT_OBJECT3D_PRESENTATION,
292
+ source: 'default',
293
+ };
294
+ }
295
+ const cleanIdList = (ids) => {
296
+ const next = ids?.map((value) => value?.trim()).filter(Boolean);
297
+ return next && next.length > 0 ? Array.from(new Set(next)) : undefined;
298
+ };
299
+ export function resolveManifestBaselines(manifest) {
300
+ const canonical = manifest?.baselines ?? {};
301
+ const defaults = canonical.defaults
302
+ ? {
303
+ lightingId: canonical.defaults.lightingId,
304
+ materialsId: canonical.defaults.materialsId,
305
+ hdrId: canonical.defaults.hdrId,
306
+ }
307
+ : undefined;
308
+ const availableLightingIds = cleanIdList(canonical.available?.lightingIds);
309
+ const availableMaterialsIds = cleanIdList(canonical.available?.materialsIds);
310
+ const availableHdrIds = cleanIdList(canonical.available?.hdrIds);
311
+ const available = availableLightingIds || availableMaterialsIds || availableHdrIds
312
+ ? {
313
+ lightingIds: availableLightingIds,
314
+ materialsIds: availableMaterialsIds,
315
+ hdrIds: availableHdrIds,
316
+ }
317
+ : undefined;
318
+ return {
319
+ defaults,
320
+ available,
321
+ lighting: canonical.lighting ?? [],
322
+ materials: canonical.materials ?? [],
323
+ hdr: canonical.hdr ?? [],
324
+ };
325
+ }
326
+ export function resolveManifestCompositions(manifest) {
327
+ const compositions = Array.isArray(manifest?.compositions) ? manifest.compositions : [];
328
+ for (const composition of compositions) {
329
+ if (composition && typeof composition === 'object' && 'render' in composition) {
330
+ throw new Error(`manifest composition '${composition.id ?? '<unknown>'}' declares removed composition.render. Use lightingBaselineId/materialsBaselineId/hdrBaselineId directly.`);
331
+ }
332
+ }
333
+ return compositions;
334
+ }
335
+ export function resolveCompositionBaselineIds(composition) {
336
+ return {
337
+ lightingBaselineId: composition?.lightingBaselineId,
338
+ materialsBaselineId: composition?.materialsBaselineId,
339
+ hdrBaselineId: composition?.hdrBaselineId,
340
+ };
341
+ }
342
+ export function resolveToolDescriptor(tool, plugin) {
343
+ const rawPlugin = plugin;
344
+ const rawTool = tool;
345
+ const pluginExtRecord = plugin.extensions ?? {};
346
+ const toolExtRecord = tool.extensions ?? {};
347
+ const { render: _ignoredPluginRender, ...pluginExt } = pluginExtRecord;
348
+ const { render: _ignoredToolRender, ...toolExt } = toolExtRecord;
349
+ const hasPluginRenderCompat = Object.prototype.hasOwnProperty.call(pluginExtRecord, 'render');
350
+ const hasToolRenderCompat = Object.prototype.hasOwnProperty.call(toolExtRecord, 'render');
351
+ const diagnostics = [];
352
+ const label = tool.label || pluginExt.label || tool.toolId;
353
+ const summary = toolExt.summary ??
354
+ pluginExt.summary ??
355
+ undefined;
356
+ const mergedExtensions = {
357
+ ...toolExt,
358
+ ...(toolExt.label === undefined && pluginExt.label !== undefined ? { label: pluginExt.label } : {}),
359
+ ...(toolExt.summary === undefined && pluginExt.summary !== undefined ? { summary: pluginExt.summary } : {}),
360
+ ...(toolExt.tags === undefined && pluginExt.tags !== undefined ? { tags: pluginExt.tags } : {}),
361
+ };
362
+ const canonicalMapInputs = mergeMapInputs(plugin.mapInputs, tool.mapInputs);
363
+ const promptPack = resolveToolPromptPack(tool);
364
+ const mapInputs = canonicalMapInputs;
365
+ const uiResolution = resolveToolUi(tool, plugin);
366
+ const baselines = resolveManifestBaselines({
367
+ baselines: plugin.baselines,
368
+ });
369
+ const startupDefaultWarnings = resolveStartupDefaultWarnings(tool, baselines);
370
+ const presentationResolution = resolveToolPresentation({
371
+ presentation: plugin.presentation,
372
+ });
373
+ if (presentationResolution.source !== 'canonical') {
374
+ diagnostics.push(`tool '${tool.toolId}' is using default object3d presentation; add top-level presentation for explicit scene policy.`);
375
+ }
376
+ if (hasPluginRenderCompat || hasToolRenderCompat) {
377
+ throw new Error(`tool '${tool.toolId}' declares removed extensions.render metadata. Delete extensions.render from the manifest before publish.`);
378
+ }
379
+ if (rawPlugin.promptPack && trimModeId(rawPlugin.promptPack.id)) {
380
+ throw new Error(`tool '${tool.toolId}' declares plugin-level promptPack. Composer only reads tool.promptPack; move prompt pack ids onto the tool manifest.`);
381
+ }
382
+ if (Object.prototype.hasOwnProperty.call(rawPlugin, 'surfaceTypes')) {
383
+ throw new Error(`tool '${tool.toolId}' declares removed plugin.surfaceTypes. Move plugin-owned surface vocabulary into Geometry surface-contract.json and publish it as top-level surfaceContract.`);
384
+ }
385
+ if (Object.prototype.hasOwnProperty.call(rawPlugin, 'surfaceMappings')) {
386
+ throw new Error(`tool '${tool.toolId}' declares removed plugin.surfaceMappings. Active runtime plugins must emit direct b2:surfaceClass values instead of manifest surface mappings.`);
387
+ }
388
+ if (hasLegacyPromptingDefinition(rawPlugin.prompting)) {
389
+ throw new Error(`tool '${tool.toolId}' declares legacy plugin-level prompting metadata. Remove prompting and declare tool.promptPack instead.`);
390
+ }
391
+ if (hasLegacyPromptingDefinition(rawTool.prompting)) {
392
+ throw new Error(`tool '${tool.toolId}' declares legacy top-level prompting metadata. Remove prompting and declare tool.promptPack instead.`);
393
+ }
394
+ const legacyPresets = plugin.presets;
395
+ if (Array.isArray(legacyPresets)) {
396
+ throw new Error(`tool '${tool.toolId}' declares removed presets[]. Remove presets[] and use top-level compositions[].`);
397
+ }
398
+ const executionResolution = resolveToolExecution(tool, plugin);
399
+ if (executionResolution.source !== 'canonical') {
400
+ diagnostics.push(`tool '${tool.toolId}' is using the default execution timeout; add canonical execution.timeoutMs in the manifest if the solve is expected to run longer.`);
401
+ }
402
+ diagnostics.push(...startupDefaultWarnings.map((warning) => warning.message));
403
+ const resolvedTagging = plugin.tagging;
404
+ const surfaceContract = normalizeSurfaceContract(plugin.surfaceContract);
405
+ validateMaterialBaselinesAgainstSurfaceContract(tool.toolId, baselines.materials ?? [], surfaceContract);
406
+ return {
407
+ ...tool,
408
+ label,
409
+ extensions: mergedExtensions,
410
+ pluginId: tool.pipeline?.[0]?.pluginId ?? plugin.pluginId,
411
+ schema: plugin.schemas.request,
412
+ responseSchema: plugin.schemas.response,
413
+ ui: uiResolution.ui,
414
+ defaults: plugin.defaults,
415
+ presentation: presentationResolution.presentation,
416
+ execution: executionResolution.execution,
417
+ metrics: tool.metrics ?? plugin.metrics,
418
+ materials: plugin.materials,
419
+ baselines,
420
+ compositions: resolveManifestCompositions(plugin),
421
+ promptPack,
422
+ mapInputs,
423
+ surfaceContract,
424
+ capabilities: plugin.capabilities,
425
+ tagging: resolvedTagging,
426
+ summary,
427
+ compatibility: {
428
+ uiSource: uiResolution.source,
429
+ baselinesSource: plugin.baselines ? 'canonical' : 'none',
430
+ compositionsSource: Array.isArray(plugin.compositions) ? 'canonical' : 'none',
431
+ presentationSource: presentationResolution.source,
432
+ executionSource: executionResolution.source,
433
+ warnings: startupDefaultWarnings,
434
+ diagnostics,
435
+ },
436
+ };
437
+ }
1
438
  export { buildDefinitionManifestFromGhInputs } from "./gh/inputsToManifest";
2
- export { loadSchemas, schemaUrls } from "./schemas";
3
- export { createDefaultManifestValidator, createManifestValidator } from "./validate";
@@ -0,0 +1,4 @@
1
+ export type { ManifestSchemas } from "./schemas";
2
+ export { loadSchemas, schemaUrls } from "./schemas";
3
+ export { createDefaultManifestValidator, createManifestValidator } from "./validate";
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,8BAA8B,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,2 @@
1
+ export { loadSchemas, schemaUrls } from "./schemas";
2
+ export { createDefaultManifestValidator, createManifestValidator } from "./validate";