@veil-runtime/ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +6 -0
  4. package/README.md +96 -2
  5. package/SECURITY.md +11 -0
  6. package/dist/application.d.ts +56 -0
  7. package/dist/application.js +9 -0
  8. package/dist/build-manifest.json +82 -0
  9. package/dist/capability-view-derivation.d.ts +40 -0
  10. package/dist/capability-view-derivation.js +185 -0
  11. package/dist/capability-view.d.ts +170 -0
  12. package/dist/capability-view.js +1 -0
  13. package/dist/composed-surface.d.ts +67 -0
  14. package/dist/composed-surface.js +1 -0
  15. package/dist/composition-inspection.d.ts +51 -0
  16. package/dist/composition-inspection.js +102 -0
  17. package/dist/derive-input-fields.d.ts +4 -0
  18. package/dist/derive-input-fields.js +63 -0
  19. package/dist/derive-output-view.d.ts +26 -0
  20. package/dist/derive-output-view.js +97 -0
  21. package/dist/execution-request.d.ts +13 -0
  22. package/dist/execution-request.js +58 -0
  23. package/dist/experience-orchestration.d.ts +13 -0
  24. package/dist/experience-orchestration.js +31 -0
  25. package/dist/format-semantic-value.d.ts +4 -0
  26. package/dist/format-semantic-value.js +15 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +18 -0
  29. package/dist/input-schema.d.ts +23 -0
  30. package/dist/input-schema.js +131 -0
  31. package/dist/interaction-resolution.d.ts +27 -0
  32. package/dist/interaction-resolution.js +179 -0
  33. package/dist/output-presentation-suggestion-schema.d.ts +12 -0
  34. package/dist/output-presentation-suggestion-schema.js +8 -0
  35. package/dist/output-schema.d.ts +26 -0
  36. package/dist/output-schema.js +213 -0
  37. package/dist/presentation-composition.d.ts +29 -0
  38. package/dist/presentation-composition.js +23 -0
  39. package/dist/presentation-context.d.ts +6 -0
  40. package/dist/presentation-context.js +1 -0
  41. package/dist/presentation-suggestion-schema.d.ts +15 -0
  42. package/dist/presentation-suggestion-schema.js +10 -0
  43. package/dist/registry.d.ts +21 -0
  44. package/dist/registry.js +71 -0
  45. package/dist/relationship-interpretation.d.ts +9 -0
  46. package/dist/relationship-interpretation.js +16 -0
  47. package/dist/validate-application.d.ts +24 -0
  48. package/dist/validate-application.js +253 -0
  49. package/dist/validation-safety.d.ts +6 -0
  50. package/dist/validation-safety.js +90 -0
  51. package/dist/workflow.d.ts +18 -0
  52. package/dist/workflow.js +3 -0
  53. package/docs/API.md +17 -0
  54. package/docs/DECISIONS.md +16 -0
  55. package/docs/THIRD_PARTY.md +16 -0
  56. package/docs/third-party/ZOD-LICENSE.txt +21 -0
  57. package/examples/standalone/index.mjs +78 -0
  58. package/examples/standalone/package.json +8 -0
  59. package/package.json +69 -4
@@ -0,0 +1,26 @@
1
+ export interface OutputFieldDefinition {
2
+ type: "string" | "number" | "integer" | "boolean" | "object" | "array";
3
+ description: string;
4
+ required?: boolean;
5
+ format?: "date" | "date-time";
6
+ semantic?: "currency" | "count" | "ratio" | "time-series" | "category-series";
7
+ role?: "dimension" | "measure";
8
+ enum?: string[];
9
+ currency?: string;
10
+ minimum?: number;
11
+ maximum?: number;
12
+ fields?: Record<string, OutputFieldDefinition>;
13
+ items?: OutputFieldDefinition;
14
+ }
15
+ export interface AggregateCapabilityOutputSchema {
16
+ type: "object";
17
+ semantic: "aggregate";
18
+ description: string;
19
+ fields: Record<string, OutputFieldDefinition>;
20
+ }
21
+ export type CapabilityOutputSchema = Record<string, OutputFieldDefinition> | AggregateCapabilityOutputSchema;
22
+ export declare function defineOutputSchema<T extends CapabilityOutputSchema>(schema: T): T;
23
+ export declare function isAggregateOutputSchema(schema: CapabilityOutputSchema): schema is AggregateCapabilityOutputSchema;
24
+ export declare function getOutputSchemaFields(schema: CapabilityOutputSchema): Record<string, OutputFieldDefinition>;
25
+ export declare function validateOutputSchema(schema: CapabilityOutputSchema): void;
26
+ export declare function validateCapabilityOutput(schema: CapabilityOutputSchema, value: unknown): void;
@@ -0,0 +1,213 @@
1
+ import { assertBoundedData, isCalendarDate, isUTCInstant } from "./validation-safety.js";
2
+ export function defineOutputSchema(schema) {
3
+ return schema;
4
+ }
5
+ export function isAggregateOutputSchema(schema) {
6
+ return schema.type === "object" && schema.semantic === "aggregate";
7
+ }
8
+ export function getOutputSchemaFields(schema) {
9
+ return isAggregateOutputSchema(schema) ? schema.fields : schema;
10
+ }
11
+ function validateTimeSeriesField(name, field) {
12
+ if (field.type !== "array")
13
+ throw new Error("Time-series semantics require an array output: " + name);
14
+ if (field.items?.type !== "object" || !field.items.fields)
15
+ throw new Error("Time-series items must be objects: " + name);
16
+ const entries = Object.entries(field.items.fields);
17
+ if (entries.length === 0)
18
+ throw new Error("Time-series item schema cannot be empty: " + name);
19
+ const dimensions = entries.filter(([, definition]) => definition.role === "dimension");
20
+ const measures = entries.filter(([, definition]) => definition.role === "measure");
21
+ if (dimensions.length !== 1)
22
+ throw new Error("Time-series requires exactly one dimension: " + name);
23
+ if (measures.length !== 1)
24
+ throw new Error("Time-series requires exactly one measure in this slice: " + name);
25
+ const [dimensionName, dimension] = dimensions[0];
26
+ if (dimension.type !== "string" || (dimension.format !== "date" && dimension.format !== "date-time"))
27
+ throw new Error("Time-series dimension must be a temporal string: " + dimensionName);
28
+ const [measureName, measure] = measures[0];
29
+ if (measure.type !== "number" && measure.type !== "integer")
30
+ throw new Error("Time-series measure must be numeric: " + measureName);
31
+ if (!measure.semantic || !["count", "currency", "ratio"].includes(measure.semantic))
32
+ throw new Error("Time-series measure requires supported numeric semantics: " + measureName);
33
+ if (measure.semantic === "currency" && !measure.currency)
34
+ throw new Error("Currency measure requires a currency code: " + measureName);
35
+ if (measure.semantic === "ratio" && ((measure.minimum !== undefined && measure.minimum < 0) || (measure.maximum !== undefined && measure.maximum > 1)))
36
+ throw new Error("Ratio measure constraints must remain within 0..1: " + measureName);
37
+ for (const [fieldName, definition] of entries)
38
+ if ((definition.type === "object" || definition.type === "array") && definition.role)
39
+ throw new Error("Nested time-series roles are unsupported: " + fieldName);
40
+ }
41
+ function validateCategorySeriesField(name, field) {
42
+ if (field.type !== "array")
43
+ throw new Error("Category-series semantics require an array output: " + name);
44
+ if (field.items?.type !== "object" || !field.items.fields)
45
+ throw new Error("Category-series items must be objects: " + name);
46
+ const entries = Object.entries(field.items.fields);
47
+ if (entries.length === 0)
48
+ throw new Error("Category-series item schema cannot be empty: " + name);
49
+ const dimensions = entries.filter(([, definition]) => definition.role === "dimension"), measures = entries.filter(([, definition]) => definition.role === "measure");
50
+ if (dimensions.length !== 1)
51
+ throw new Error("Category-series requires exactly one dimension: " + name);
52
+ if (measures.length !== 1)
53
+ throw new Error("Category-series requires exactly one measure in this slice: " + name);
54
+ const [dimensionName, dimension] = dimensions[0];
55
+ if (dimension.type !== "string")
56
+ throw new Error("Category dimension must be a string: " + dimensionName);
57
+ if (dimension.enum && (dimension.enum.length === 0 || new Set(dimension.enum).size !== dimension.enum.length))
58
+ throw new Error("Category enum must contain unique values: " + dimensionName);
59
+ const [measureName, measure] = measures[0];
60
+ if (measure.type !== "number" && measure.type !== "integer")
61
+ throw new Error("Category-series measure must be numeric: " + measureName);
62
+ if (!measure.semantic || !["count", "currency", "ratio"].includes(measure.semantic))
63
+ throw new Error("Category-series measure requires supported numeric semantics: " + measureName);
64
+ if (measure.semantic === "currency" && !measure.currency)
65
+ throw new Error("Currency measure requires a currency code: " + measureName);
66
+ if (measure.semantic === "ratio" && ((measure.minimum !== undefined && measure.minimum < 0) || (measure.maximum !== undefined && measure.maximum > 1)))
67
+ throw new Error("Ratio measure constraints must remain within 0..1: " + measureName);
68
+ for (const [fieldName, definition] of entries)
69
+ if ((definition.type === "object" || definition.type === "array") && definition.role)
70
+ throw new Error("Nested category-series roles are unsupported: " + fieldName);
71
+ }
72
+ export function validateOutputSchema(schema) {
73
+ assertBoundedData(schema);
74
+ const candidate = schema;
75
+ if (candidate.semantic === "aggregate" && candidate.type !== "object")
76
+ throw new Error("Aggregate semantics require an object output");
77
+ const aggregate = isAggregateOutputSchema(schema);
78
+ const entries = Object.entries(getOutputSchemaFields(schema));
79
+ if (aggregate && entries.length === 0)
80
+ throw new Error("Aggregate output must contain at least one renderable metric");
81
+ for (const [name, field] of entries) {
82
+ if (field.semantic === "time-series")
83
+ validateTimeSeriesField(name, field);
84
+ if (field.semantic === "category-series")
85
+ validateCategorySeriesField(name, field);
86
+ if (field.role)
87
+ throw new Error("Dimension/measure roles are only valid inside time-series items: " + name);
88
+ if (aggregate && (field.type === "object" || field.type === "array"))
89
+ throw new Error("Nested aggregate metric is unsupported: " + name);
90
+ if (aggregate && (!field.semantic || !["count", "currency", "ratio"].includes(field.semantic)))
91
+ throw new Error("Unsupported aggregate field semantic: " + name);
92
+ if (field.semantic === "count" && field.type !== "integer" && field.type !== "number")
93
+ throw new Error("Count semantics require a numeric field: " + name);
94
+ if (field.semantic === "currency" && ((field.type !== "integer" && field.type !== "number") || !field.currency))
95
+ throw new Error("Currency semantics require a numeric field and currency code: " + name);
96
+ if (field.semantic === "ratio") {
97
+ if (field.type !== "number")
98
+ throw new Error("Ratio semantics require a number field: " + name);
99
+ if ((field.minimum !== undefined && field.minimum < 0) || (field.maximum !== undefined && field.maximum > 1) || (field.minimum !== undefined && field.maximum !== undefined && field.minimum > field.maximum))
100
+ throw new Error("Ratio constraints must remain within 0..1: " + name);
101
+ }
102
+ }
103
+ }
104
+ function missingOutput(value) {
105
+ return value === undefined || value === null;
106
+ }
107
+ function validateOutputValue(field, value, path) {
108
+ if (missingOutput(value)) {
109
+ if (field.required !== false) {
110
+ throw new Error(`Required output omitted: ${path}`);
111
+ }
112
+ return;
113
+ }
114
+ if (field.type === "string") {
115
+ if (typeof value !== "string")
116
+ throw new Error(`Output "${path}" must be a string`);
117
+ if (field.enum && !field.enum.includes(value))
118
+ throw new Error(`Output "${path}" is not an allowed enum value`);
119
+ if (field.format === "date" && !isCalendarDate(value))
120
+ throw new Error(`Output "${path}" must use YYYY-MM-DD`);
121
+ if (field.format === "date-time" && !isUTCInstant(value))
122
+ throw new Error(`Output "${path}" must be an ISO-8601 UTC instant`);
123
+ return;
124
+ }
125
+ if (field.type === "number" || field.type === "integer") {
126
+ if (typeof value !== "number" || !Number.isFinite(value))
127
+ throw new Error(`Output "${path}" must be numeric`);
128
+ if (field.type === "integer" && !Number.isInteger(value))
129
+ throw new Error(`Output "${path}" must be an integer`);
130
+ if (field.minimum !== undefined && value < field.minimum)
131
+ throw new Error(`Output "${path}" is below minimum`);
132
+ if (field.maximum !== undefined && value > field.maximum)
133
+ throw new Error(`Output "${path}" exceeds maximum`);
134
+ if (field.semantic === "ratio" && (value < 0 || value > 1))
135
+ throw new Error(`Output ratio "${path}" must remain within 0..1`);
136
+ return;
137
+ }
138
+ if (field.type === "boolean") {
139
+ if (typeof value !== "boolean")
140
+ throw new Error(`Output "${path}" must be boolean`);
141
+ return;
142
+ }
143
+ if (field.type === "object") {
144
+ if (!value || typeof value !== "object" || Array.isArray(value))
145
+ throw new Error(`Output "${path}" must be an object`);
146
+ const record = value;
147
+ for (const key of Object.keys(record)) {
148
+ if (!Object.hasOwn(field.fields ?? {}, key))
149
+ throw new Error(`Unexpected nested output: ${path}.${key}`);
150
+ }
151
+ for (const [name, child] of Object.entries(field.fields ?? {})) {
152
+ validateOutputValue(child, Object.hasOwn(record, name) ? record[name] : undefined, `${path}.${name}`);
153
+ }
154
+ return;
155
+ }
156
+ if (!Array.isArray(value))
157
+ throw new Error(`Output "${path}" must be an array`);
158
+ if (!field.items)
159
+ throw new Error(`Output array "${path}" lacks an item schema`);
160
+ value.forEach((item, index) => validateOutputValue(field.items, item, `${path}[${index}]`));
161
+ }
162
+ export function validateCapabilityOutput(schema, value) {
163
+ assertBoundedData(schema);
164
+ assertBoundedData(value);
165
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
166
+ throw new Error("Capability output must be an object");
167
+ }
168
+ const result = value;
169
+ const fields = getOutputSchemaFields(schema);
170
+ for (const key of Object.keys(result)) {
171
+ if (!Object.hasOwn(fields, key))
172
+ throw new Error(`Unexpected supplied output: ${key}`);
173
+ }
174
+ for (const [name, field] of Object.entries(fields)) {
175
+ validateOutputValue(field, Object.hasOwn(result, name) ? result[name] : undefined, name);
176
+ }
177
+ for (const [name, field] of Object.entries(fields)) {
178
+ if (missingOutput(result[name]) && field.required === false)
179
+ continue;
180
+ if (field.semantic === "category-series") {
181
+ const points = result[name];
182
+ const entries = Object.entries(field.items.fields);
183
+ const [dimensionName] = entries.find(([, definition]) => definition.role === "dimension");
184
+ const seen = new Set();
185
+ for (const point of points) {
186
+ const category = point[dimensionName];
187
+ if (seen.has(category))
188
+ throw new Error("Duplicate category is not allowed: " + category);
189
+ seen.add(category);
190
+ }
191
+ continue;
192
+ }
193
+ if (field.semantic === "time-series") {
194
+ const points = result[name];
195
+ const entries = Object.entries(field.items.fields);
196
+ const [dimensionName, dimension] = entries.find(([, definition]) => definition.role === "dimension");
197
+ let previous;
198
+ for (const point of points) {
199
+ const current = point[dimensionName];
200
+ const previousInstant = previous === undefined
201
+ ? undefined
202
+ : Date.parse(previous);
203
+ const currentInstant = dimension.format === "date-time"
204
+ ? Date.parse(current)
205
+ : Date.parse(`${current}T00:00:00.000Z`);
206
+ if (previousInstant !== undefined && currentInstant < previousInstant) {
207
+ throw new Error("Time-series points must preserve chronological order");
208
+ }
209
+ previous = current;
210
+ }
211
+ }
212
+ }
213
+ }
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import type { PresentationEnvelope, PresentationPlan, PresentationProvenance, SurfaceExecution, RelatedSurfaceExecution } from "./composed-surface.js";
3
+ export declare const presentationPlanSchema: z.ZodObject<{
4
+ title: z.ZodOptional<z.ZodString>;
5
+ description: z.ZodOptional<z.ZodString>;
6
+ groups: z.ZodArray<z.ZodObject<{
7
+ id: z.ZodString;
8
+ title: z.ZodOptional<z.ZodString>;
9
+ description: z.ZodOptional<z.ZodString>;
10
+ emphasis: z.ZodEnum<{
11
+ normal: "normal";
12
+ prominent: "prominent";
13
+ subtle: "subtle";
14
+ }>;
15
+ density: z.ZodEnum<{
16
+ compact: "compact";
17
+ comfortable: "comfortable";
18
+ }>;
19
+ sections: z.ZodArray<z.ZodString>;
20
+ }, z.core.$strict>>;
21
+ }, z.core.$strict>;
22
+ export declare function sectionId(kind: "primary" | "related", capability: string): string;
23
+ export declare function buildPresentationEnvelope(primary: SurfaceExecution, related: RelatedSurfaceExecution[]): PresentationEnvelope;
24
+ export declare function deterministicPresentation(envelope: PresentationEnvelope): PresentationPlan;
25
+ export declare function validatePresentationPlan(envelope: PresentationEnvelope, value: unknown): PresentationPlan;
26
+ export declare function composePresentation(envelope: PresentationEnvelope, strategy: "deterministic" | "generative", generate?: (envelope: PresentationEnvelope) => Promise<unknown>): Promise<{
27
+ plan: PresentationPlan;
28
+ provenance: PresentationProvenance;
29
+ }>;
@@ -0,0 +1,23 @@
1
+ import { assertBoundedData, copyData } from "./validation-safety.js";
2
+ import { z } from "zod";
3
+ const groupSchema = z.object({ id: z.string().regex(/^[a-zA-Z0-9_-]+$/).max(48), title: z.string().min(1).max(80).optional(), description: z.string().min(1).max(240).optional(), emphasis: z.enum(["normal", "prominent", "subtle"]), density: z.enum(["comfortable", "compact"]), sections: z.array(z.string().min(1).max(120)).min(1).max(256) }).strict();
4
+ export const presentationPlanSchema = z.object({ title: z.string().min(1).max(100).optional(), description: z.string().min(1).max(240).optional(), groups: z.array(groupSchema).max(12) }).strict();
5
+ export function sectionId(kind, capability) { return kind + ":" + capability; }
6
+ export function buildPresentationEnvelope(primary, related) { return { applicationSurface: primary.capability, sections: [{ id: sectionId("primary", primary.capability), capability: primary.capability, surface: primary.view.surface, label: primary.view.title, required: true, movable: false, groupable: false, defaultOrder: 0 }, ...related.map((section, index) => ({ id: sectionId("related", section.capability), capability: section.capability, surface: section.view?.surface ?? "error", label: section.label, required: true, movable: true, groupable: true, defaultOrder: index + 1 }))], immutable: ["capabilities", "fields", "metrics", "bindings", "workflow", "risk", "actions", "surface semantics", "execution"] }; }
7
+ export function deterministicPresentation(envelope) { return { groups: envelope.sections.filter((section) => section.movable).map((section, index) => ({ id: "section_" + (index + 1), title: section.label, emphasis: "normal", density: "comfortable", sections: [section.id] })) }; }
8
+ export function validatePresentationPlan(envelope, value) { assertBoundedData(envelope); assertBoundedData(value); const parsed = presentationPlanSchema.safeParse(value); if (!parsed.success)
9
+ throw new Error("Invalid generated composition: " + parsed.error.message); const allowed = envelope.sections.filter((section) => section.movable).map((section) => section.id), received = parsed.data.groups.flatMap((group) => group.sections); if (new Set(received).size !== received.length)
10
+ throw new Error("Generated composition duplicated a section"); if (received.some((id) => !allowed.includes(id)))
11
+ throw new Error("Generated composition referenced an unknown or fixed section"); if (allowed.length !== received.length || allowed.some((id) => !received.includes(id)))
12
+ throw new Error("Generated composition omitted a required section"); const groupIds = parsed.data.groups.map((group) => group.id); if (new Set(groupIds).size !== groupIds.length)
13
+ throw new Error("Generated composition duplicated a group id"); return parsed.data; }
14
+ export async function composePresentation(envelope, strategy, generate) { const baseline = deterministicPresentation(envelope); if (strategy === "deterministic")
15
+ return { plan: baseline, provenance: { strategy: "deterministic", requestedStrategy: "deterministic", fallback: false, validated: true } }; try {
16
+ if (!generate)
17
+ throw new Error("No presentation provider configured");
18
+ const proposed = await generate(copyData(envelope));
19
+ return { plan: validatePresentationPlan(envelope, proposed), provenance: { strategy: "generative", requestedStrategy: "generative", fallback: false, validated: true } };
20
+ }
21
+ catch (error) {
22
+ return { plan: baseline, provenance: { strategy: "deterministic", requestedStrategy: "generative", fallback: true, validated: true, reason: error instanceof Error ? error.message : "generation-failed" } };
23
+ } }
@@ -0,0 +1,6 @@
1
+ export type DeviceContext = "desktop" | "mobile";
2
+ export type InteractionMode = "full" | "compact";
3
+ export interface ViewContext {
4
+ device: DeviceContext;
5
+ mode: InteractionMode;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export declare const generatedFieldPresentationSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ label: z.ZodString;
5
+ }, z.core.$strict>;
6
+ export declare const generatedPresentationSchema: z.ZodObject<{
7
+ title: z.ZodString;
8
+ submitLabel: z.ZodString;
9
+ fields: z.ZodArray<z.ZodObject<{
10
+ name: z.ZodString;
11
+ label: z.ZodString;
12
+ }, z.core.$strict>>;
13
+ }, z.core.$strict>;
14
+ export type GeneratedPresentation = z.infer<typeof generatedPresentationSchema>;
15
+ export type GeneratedFieldPresentation = z.infer<typeof generatedFieldPresentationSchema>;
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ export const generatedFieldPresentationSchema = z.object({
3
+ name: z.string().min(1).max(160),
4
+ label: z.string().min(1).max(160),
5
+ }).strict();
6
+ export const generatedPresentationSchema = z.object({
7
+ title: z.string().min(1).max(160),
8
+ submitLabel: z.string().min(1).max(160),
9
+ fields: z.array(generatedFieldPresentationSchema).max(256),
10
+ }).strict();
@@ -0,0 +1,21 @@
1
+ import type { Capability } from "@veil-runtime/core";
2
+ import type { ApplicationManifest, CapabilityRelationship, CapabilityTransition, DerivedApplication } from "./application.js";
3
+ import { type CapabilityOutputSchema } from "./output-schema.js";
4
+ import { type ApplicationValidationResult } from "./validate-application.js";
5
+ export interface UIRegistryDefinitions {
6
+ capabilities: readonly Capability[];
7
+ applications: readonly ApplicationManifest[];
8
+ outputSchemas?: Readonly<Record<string, CapabilityOutputSchema>>;
9
+ relationships?: readonly CapabilityRelationship[];
10
+ transitions?: readonly CapabilityTransition[];
11
+ }
12
+ export interface UIRegistry {
13
+ listApplications(): ApplicationManifest[];
14
+ getApplication(applicationId: string): ApplicationManifest | undefined;
15
+ getOutputSchema(capabilityName: string): CapabilityOutputSchema | undefined;
16
+ getRelationships(capabilityName: string): CapabilityRelationship[];
17
+ getTransition(capabilityName: string): CapabilityTransition | undefined;
18
+ deriveApplication(applicationId: string): DerivedApplication | undefined;
19
+ validateApplication(application: DerivedApplication): ApplicationValidationResult;
20
+ }
21
+ export declare function createUIRegistry(definitions: UIRegistryDefinitions): UIRegistry;
@@ -0,0 +1,71 @@
1
+ import { copyData } from "./validation-safety.js";
2
+ import { validateRichInputSchema, } from "./input-schema.js";
3
+ import { validateOutputSchema, } from "./output-schema.js";
4
+ import { validateApplication, } from "./validate-application.js";
5
+ export function createUIRegistry(definitions) {
6
+ // Snapshot caller metadata; executable functions stay host-owned and are never exposed.
7
+ definitions = {
8
+ capabilities: definitions.capabilities.map((capability) => ({
9
+ ...capability,
10
+ ...(capability.inputSchema ? { inputSchema: copyData(capability.inputSchema) } : {}),
11
+ })),
12
+ applications: copyData(definitions.applications),
13
+ ...(definitions.outputSchemas ? { outputSchemas: copyData(definitions.outputSchemas) } : {}),
14
+ ...(definitions.relationships ? { relationships: copyData(definitions.relationships) } : {}),
15
+ ...(definitions.transitions ? { transitions: copyData(definitions.transitions) } : {}),
16
+ };
17
+ const capabilities = new Map(definitions.capabilities.map((capability) => [capability.name, capability]));
18
+ const applications = new Map(definitions.applications.map((application) => [application.id, application]));
19
+ const outputSchemas = definitions.outputSchemas ?? {};
20
+ const relationships = definitions.relationships ?? [];
21
+ const transitions = definitions.transitions ?? [];
22
+ const getCapability = (name) => capabilities.get(name);
23
+ function deriveApplication(applicationId) {
24
+ const application = applications.get(applicationId);
25
+ if (!application)
26
+ return undefined;
27
+ const derivedCapabilities = application.capabilities.map((capabilityName) => {
28
+ const capability = getCapability(capabilityName);
29
+ if (!capability) {
30
+ throw new Error("Application \"" + applicationId + "\" references unknown capability: " + capabilityName);
31
+ }
32
+ const inputSchema = (capability.inputSchema ?? {});
33
+ validateRichInputSchema(inputSchema);
34
+ const outputSchema = Object.hasOwn(outputSchemas, capability.name)
35
+ ? outputSchemas[capability.name]
36
+ : undefined;
37
+ if (outputSchema)
38
+ validateOutputSchema(outputSchema);
39
+ const transition = transitions.find((candidate) => candidate.capability === capability.name);
40
+ return {
41
+ name: capability.name,
42
+ version: capability.version,
43
+ description: capability.description,
44
+ risk: capability.risk,
45
+ inputSchema,
46
+ ...(outputSchema ? { outputSchema } : {}),
47
+ relationships: relationships.filter((relationship) => relationship.sourceCapability === capability.name),
48
+ ...(transition ? { transition } : {}),
49
+ };
50
+ });
51
+ return copyData({
52
+ id: application.id,
53
+ title: application.title,
54
+ ...(application.description
55
+ ? { description: application.description }
56
+ : {}),
57
+ entryCapability: application.entryCapability,
58
+ capabilities: derivedCapabilities,
59
+ ...(application.workflow ? { workflow: application.workflow } : {}),
60
+ });
61
+ }
62
+ return {
63
+ listApplications: () => copyData([...applications.values()]),
64
+ getApplication: (applicationId) => copyData(applications.get(applicationId)),
65
+ getOutputSchema: (capabilityName) => copyData(Object.hasOwn(outputSchemas, capabilityName) ? outputSchemas[capabilityName] : undefined),
66
+ getRelationships: (capabilityName) => copyData(relationships.filter((relationship) => relationship.sourceCapability === capabilityName)),
67
+ getTransition: (capabilityName) => copyData(transitions.find((transition) => transition.capability === capabilityName)),
68
+ deriveApplication,
69
+ validateApplication: (application) => validateApplication(application, getCapability),
70
+ };
71
+ }
@@ -0,0 +1,9 @@
1
+ export type RelationshipRole = "action" | "navigate" | "related";
2
+ export type RelationshipInteraction = "application-action" | "contextual-action" | "row-action" | "navigation" | "related-read";
3
+ export interface InterpretableRelationship {
4
+ role?: RelationshipRole;
5
+ bindings: Record<string, string>;
6
+ conditions?: readonly unknown[];
7
+ }
8
+ export declare function interpretRelationship(relationship: InterpretableRelationship, source: "application-entry" | "table-row" | "detail"): RelationshipInteraction;
9
+ export declare function isExplicitAction(interaction: RelationshipInteraction): boolean;
@@ -0,0 +1,16 @@
1
+ export function interpretRelationship(relationship, source) {
2
+ if (relationship.role === "related")
3
+ return "related-read";
4
+ if (relationship.role === "navigate")
5
+ return "navigation";
6
+ const contextual = Object.keys(relationship.bindings).length > 0
7
+ || (relationship.conditions?.length ?? 0) > 0;
8
+ if (source === "table-row" && contextual)
9
+ return "row-action";
10
+ if (contextual)
11
+ return "contextual-action";
12
+ return "application-action";
13
+ }
14
+ export function isExplicitAction(interaction) {
15
+ return interaction !== "related-read";
16
+ }
@@ -0,0 +1,24 @@
1
+ import type { Capability } from "@veil-runtime/core";
2
+ import type { DerivedApplication } from "./application.js";
3
+ export type ApplicationValidationSeverity = "error" | "warning";
4
+ export type ApplicationValidationIssueCode = "ENTRY_CAPABILITY_NOT_FOUND" | "DUPLICATE_CAPABILITY" | "RELATIONSHIP_TARGET_NOT_FOUND" | "TRANSITION_TARGET_NOT_FOUND" | "RELATIONSHIP_SOURCE_FIELD_NOT_FOUND" | "RELATIONSHIP_TARGET_FIELD_NOT_FOUND" | "RELATIONSHIP_REQUIRED_INPUT_UNBOUND" | "CAPABILITY_RISK_MISMATCH" | "WORKFLOW_STATE_FIELD_NOT_FOUND" | "WORKFLOW_ENTRY_STATE_NOT_FOUND" | "DUPLICATE_WORKFLOW_STATE" | "WORKFLOW_STATE_NOT_FOUND" | "WORKFLOW_CAPABILITY_NOT_FOUND" | "CONDITION_FIELD_NOT_FOUND" | "RESULT_BINDING_SOURCE_FIELD_NOT_FOUND" | "RESULT_BINDING_TARGET_FIELD_NOT_FOUND" | "RESULT_BINDING_REQUIRED_INPUT_UNBOUND" | "RELATED_TARGET_NOT_READ" | "RELATED_SELF_REFERENCE" | "RELATED_DIRECT_CYCLE" | "DUPLICATE_RELATED_CAPABILITY" | "RELATED_BINDING_TYPE_MISMATCH" | "RELATED_REQUIRED_INPUT_UNBOUND" | "INVALID_RELATIONSHIP_ROLE" | "UNSUPPORTED_NESTED_RELATED_COMPOSITION";
5
+ export interface ApplicationValidationIssue {
6
+ code: ApplicationValidationIssueCode;
7
+ severity: ApplicationValidationSeverity;
8
+ message: string;
9
+ applicationId: string;
10
+ capability?: string;
11
+ targetCapability?: string;
12
+ field?: string;
13
+ }
14
+ export interface ApplicationValidationResult {
15
+ valid: boolean;
16
+ issues: ApplicationValidationIssue[];
17
+ }
18
+ export interface AuthoritativeCapabilityMetadata {
19
+ name: Capability["name"];
20
+ risk: Capability["risk"];
21
+ inputSchema?: Capability["inputSchema"];
22
+ }
23
+ export type CapabilityMetadataResolver = (capabilityName: string) => AuthoritativeCapabilityMetadata | undefined;
24
+ export declare function validateApplication(application: DerivedApplication, resolveCapability: CapabilityMetadataResolver): ApplicationValidationResult;