@veil-runtime/ui 0.0.0-stage → 0.3.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 (81) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +6 -0
  4. package/README.md +100 -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 +95 -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/composition.d.ts +43 -0
  18. package/dist/composition.js +227 -0
  19. package/dist/derive-input-fields.d.ts +4 -0
  20. package/dist/derive-input-fields.js +63 -0
  21. package/dist/derive-output-view.d.ts +26 -0
  22. package/dist/derive-output-view.js +97 -0
  23. package/dist/dom.d.ts +31 -0
  24. package/dist/dom.js +554 -0
  25. package/dist/execution-request.d.ts +13 -0
  26. package/dist/execution-request.js +58 -0
  27. package/dist/experience-orchestration.d.ts +13 -0
  28. package/dist/experience-orchestration.js +31 -0
  29. package/dist/format-semantic-value.d.ts +4 -0
  30. package/dist/format-semantic-value.js +15 -0
  31. package/dist/index.d.ts +20 -0
  32. package/dist/index.js +20 -0
  33. package/dist/input-schema.d.ts +25 -0
  34. package/dist/input-schema.js +131 -0
  35. package/dist/interaction-resolution.d.ts +27 -0
  36. package/dist/interaction-resolution.js +179 -0
  37. package/dist/interface-transaction.d.ts +9 -0
  38. package/dist/interface-transaction.js +14 -0
  39. package/dist/interface.d.ts +196 -0
  40. package/dist/interface.js +375 -0
  41. package/dist/output-presentation-suggestion-schema.d.ts +12 -0
  42. package/dist/output-presentation-suggestion-schema.js +8 -0
  43. package/dist/output-schema.d.ts +26 -0
  44. package/dist/output-schema.js +213 -0
  45. package/dist/presentation-composition.d.ts +29 -0
  46. package/dist/presentation-composition.js +25 -0
  47. package/dist/presentation-context.d.ts +6 -0
  48. package/dist/presentation-context.js +1 -0
  49. package/dist/presentation-suggestion-schema.d.ts +15 -0
  50. package/dist/presentation-suggestion-schema.js +10 -0
  51. package/dist/registry.d.ts +21 -0
  52. package/dist/registry.js +71 -0
  53. package/dist/relationship-interpretation.d.ts +9 -0
  54. package/dist/relationship-interpretation.js +16 -0
  55. package/dist/validate-application.d.ts +24 -0
  56. package/dist/validate-application.js +253 -0
  57. package/dist/validation-safety.d.ts +6 -0
  58. package/dist/validation-safety.js +90 -0
  59. package/dist/workflow.d.ts +18 -0
  60. package/dist/workflow.js +3 -0
  61. package/docs/API.md +19 -0
  62. package/docs/COMPOSITION.md +67 -0
  63. package/docs/DECISIONS.md +16 -0
  64. package/docs/RELEASE-0.3.0-rc.1.md +25 -0
  65. package/docs/RELEASE-0.3.0-rc.2.md +9 -0
  66. package/docs/RELEASE-0.3.0.md +7 -0
  67. package/docs/THIRD_PARTY.md +16 -0
  68. package/docs/UNIVERSAL-INTERFACE.md +105 -0
  69. package/docs/third-party/ZOD-LICENSE.txt +21 -0
  70. package/examples/composed/app.mjs +44 -0
  71. package/examples/composed/build.mjs +7 -0
  72. package/examples/composed/index.html +34 -0
  73. package/examples/standalone/index.mjs +78 -0
  74. package/examples/standalone/package.json +8 -0
  75. package/examples/universal/agent.mjs +52 -0
  76. package/examples/universal/application.mjs +16 -0
  77. package/examples/universal/build.mjs +6 -0
  78. package/examples/universal/human.mjs +16 -0
  79. package/examples/universal/index.html +4 -0
  80. package/examples/universal/main.mjs +7 -0
  81. package/package.json +90 -4
@@ -0,0 +1,185 @@
1
+ import { z } from "zod";
2
+ import { assertBoundedData, copyData } from "./validation-safety.js";
3
+ import { deriveUIFields } from "./derive-input-fields.js";
4
+ import { deriveOutputView } from "./derive-output-view.js";
5
+ import { interpretRelationship, isExplicitAction } from "./relationship-interpretation.js";
6
+ function humanise(value) {
7
+ return value
8
+ .replace(/[._-]+/g, " ")
9
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
10
+ .replace(/\b\w/g, (character) => character.toUpperCase());
11
+ }
12
+ export function deriveDeterministicCapabilityView(options) {
13
+ const { capability, outputSchema, transition } = options;
14
+ assertBoundedData(capability.inputSchema ?? {});
15
+ assertBoundedData(outputSchema);
16
+ assertBoundedData(options.relationships ?? []);
17
+ assertBoundedData(transition);
18
+ const relationships = options.relationships ?? [];
19
+ const fields = deriveUIFields((capability.inputSchema ?? {}));
20
+ const confirmation = capability.risk === "destructive"
21
+ ? {
22
+ required: true,
23
+ title: "Confirm destructive action",
24
+ message: "This action is destructive and may not be reversible.",
25
+ }
26
+ : undefined;
27
+ const outputView = deriveOutputView(outputSchema);
28
+ if (outputView.surface === "table" && outputView.table) {
29
+ outputView.table.actions = relationships
30
+ .filter((relationship) => isExplicitAction(interpretRelationship(relationship, "table-row")))
31
+ .map((relationship) => ({
32
+ label: relationship.label,
33
+ capability: relationship.targetCapability,
34
+ bindings: copyData(relationship.bindings),
35
+ interaction: interpretRelationship(relationship, "table-row"),
36
+ ...(relationship.conditions
37
+ ? { conditions: copyData(relationship.conditions) }
38
+ : {}),
39
+ }));
40
+ if (outputView.table.actions.length === 0)
41
+ delete outputView.table.actions;
42
+ }
43
+ if (outputView.surface === "detail" && outputView.detail) {
44
+ outputView.detail.actions = relationships
45
+ .filter((relationship) => isExplicitAction(interpretRelationship(relationship, "detail")))
46
+ .map((relationship) => ({
47
+ label: relationship.label,
48
+ capability: relationship.targetCapability,
49
+ bindings: copyData(relationship.bindings),
50
+ interaction: interpretRelationship(relationship, "detail"),
51
+ ...(relationship.conditions
52
+ ? { conditions: copyData(relationship.conditions) }
53
+ : {}),
54
+ }));
55
+ if (outputView.detail.actions.length === 0)
56
+ delete outputView.detail.actions;
57
+ }
58
+ return {
59
+ capability: capability.name,
60
+ title: humanise(capability.name),
61
+ description: capability.description,
62
+ risk: capability.risk,
63
+ surface: outputView.surface,
64
+ fields,
65
+ derivation: {
66
+ surface: outputView.derivation,
67
+ fields: "input-schema",
68
+ actions: "relationship-metadata",
69
+ ...(confirmation ? { confirmation: "authoritative-risk" } : {}),
70
+ ...(transition?.onSuccess
71
+ ? { successTransition: "transition-metadata" }
72
+ : {}),
73
+ },
74
+ ...(confirmation ? { confirmation } : {}),
75
+ ...(outputView.table ? { table: outputView.table } : {}),
76
+ ...(outputView.detail ? { detail: outputView.detail } : {}),
77
+ ...(outputView.aggregate ? { aggregate: outputView.aggregate } : {}),
78
+ ...(outputView.timeSeries ? { timeSeries: outputView.timeSeries } : {}),
79
+ ...(outputView.categorySeries ? { categorySeries: outputView.categorySeries } : {}),
80
+ ...(transition?.onSuccess ? { onSuccess: copyData(transition.onSuccess) } : {}),
81
+ submitLabel: humanise(capability.name),
82
+ };
83
+ }
84
+ function applySuggestion(view, suggestion) {
85
+ if (suggestion.title !== undefined && suggestion.title.trim() === "") {
86
+ throw new Error("Presentation title cannot be empty");
87
+ }
88
+ if (suggestion.submitLabel !== undefined && suggestion.submitLabel.trim() === "") {
89
+ throw new Error("Presentation submit label cannot be empty");
90
+ }
91
+ let fields = view.fields;
92
+ if (suggestion.fields) {
93
+ const expected = view.fields.map((field) => field.name).sort();
94
+ const received = suggestion.fields.map((field) => field.name).sort();
95
+ if (expected.length !== received.length
96
+ || expected.some((name, index) => name !== received[index])
97
+ || new Set(received).size !== received.length) {
98
+ throw new Error("Presentation suggestion changed authoritative field membership");
99
+ }
100
+ const suggestions = new Map(suggestion.fields.map((field) => [field.name, field.label]));
101
+ if ([...suggestions.values()].some((label) => label.trim() === "")) {
102
+ throw new Error("Presentation field labels cannot be empty");
103
+ }
104
+ fields = view.fields.map((field) => ({
105
+ ...field,
106
+ label: suggestions.get(field.name),
107
+ }));
108
+ }
109
+ return {
110
+ ...view,
111
+ fields,
112
+ ...(suggestion.title ? { title: suggestion.title } : {}),
113
+ ...(suggestion.submitLabel ? { submitLabel: suggestion.submitLabel } : {}),
114
+ };
115
+ }
116
+ export async function deriveCapabilityView(options) {
117
+ const deterministic = deriveDeterministicCapabilityView(options);
118
+ const capabilityVersion = options.capability.version;
119
+ const generatedAt = new Date().toISOString();
120
+ if (options.strategy !== "generative") {
121
+ return {
122
+ ...deterministic,
123
+ provenance: {
124
+ strategy: "deterministic",
125
+ requestedStrategy: "deterministic",
126
+ fallback: false,
127
+ capabilityVersion,
128
+ generatedAt,
129
+ source: "generated",
130
+ },
131
+ };
132
+ }
133
+ try {
134
+ if (!options.provider)
135
+ throw new Error("No presentation provider configured");
136
+ const suggestion = await options.provider.suggestCapabilityPresentation({
137
+ capability: {
138
+ name: options.capability.name, version: capabilityVersion,
139
+ description: options.capability.description, risk: options.capability.risk,
140
+ ...(options.capability.inputSchema ? { inputSchema: copyData(options.capability.inputSchema) } : {}),
141
+ async execute() { throw new Error("Presentation metadata cannot execute capabilities"); },
142
+ },
143
+ deterministicView: copyData(deterministic),
144
+ ...(options.context ? { context: copyData(options.context) } : {}),
145
+ });
146
+ assertBoundedData(suggestion);
147
+ const safeSuggestion = z.object({
148
+ title: z.string().trim().min(1).max(100).optional(),
149
+ submitLabel: z.string().trim().min(1).max(80).optional(),
150
+ fields: z.array(z.object({ name: z.string().min(1).max(120), label: z.string().trim().min(1).max(160) }).strict()).max(256).optional(),
151
+ }).strict().parse(suggestion);
152
+ const view = applySuggestion(deterministic, {
153
+ ...(safeSuggestion.title !== undefined ? { title: safeSuggestion.title } : {}),
154
+ ...(safeSuggestion.submitLabel !== undefined ? { submitLabel: safeSuggestion.submitLabel } : {}),
155
+ ...(safeSuggestion.fields !== undefined ? { fields: safeSuggestion.fields } : {}),
156
+ });
157
+ return {
158
+ ...view,
159
+ provenance: {
160
+ strategy: "generative",
161
+ requestedStrategy: "generative",
162
+ provider: options.provider.name,
163
+ ...(options.provider.model ? { model: options.provider.model } : {}),
164
+ fallback: false,
165
+ capabilityVersion,
166
+ generatedAt,
167
+ source: "generated",
168
+ },
169
+ };
170
+ }
171
+ catch (error) {
172
+ return {
173
+ ...deterministic,
174
+ provenance: {
175
+ strategy: "deterministic",
176
+ requestedStrategy: "generative",
177
+ fallback: true,
178
+ reason: error instanceof Error ? error.message : "presentation-provider-failed",
179
+ capabilityVersion,
180
+ generatedAt,
181
+ source: "generated",
182
+ },
183
+ };
184
+ }
185
+ }
@@ -0,0 +1,170 @@
1
+ export type UIFieldType = "text" | "textarea" | "number" | "checkbox" | "date" | "datetime" | "currency" | "object" | "array" | "email" | "select" | "unknown";
2
+ export interface UIField {
3
+ name: string;
4
+ label: string;
5
+ type: UIFieldType;
6
+ required: boolean;
7
+ description: string;
8
+ authoritativeType: string;
9
+ options?: Array<string | number | boolean>;
10
+ format?: "date" | "date-time" | "multiline";
11
+ semantic?: "currency";
12
+ currency?: string;
13
+ minLength?: number;
14
+ maxLength?: number;
15
+ minimum?: number;
16
+ maximum?: number;
17
+ minItems?: number;
18
+ maxItems?: number;
19
+ fields?: UIField[];
20
+ item?: UIField;
21
+ derivation: {
22
+ source: "authoritative-input-schema";
23
+ reason: string;
24
+ };
25
+ }
26
+ export interface UISection {
27
+ id: string;
28
+ title: string;
29
+ fields: string[];
30
+ }
31
+ export interface UIConfirmation {
32
+ required: boolean;
33
+ title: string;
34
+ message: string;
35
+ }
36
+ export interface UIAction {
37
+ label: string;
38
+ capability: string;
39
+ bindings: Record<string, string>;
40
+ interaction: import("./relationship-interpretation.js").RelationshipInteraction;
41
+ conditions?: Array<{
42
+ field: string;
43
+ operator: "equals";
44
+ value: string | number | boolean;
45
+ } | {
46
+ field: string;
47
+ operator: "exists";
48
+ }>;
49
+ }
50
+ export interface UITableColumn {
51
+ key: string;
52
+ label: string;
53
+ }
54
+ export interface UITable {
55
+ dataPath: string;
56
+ columns: UITableColumn[];
57
+ actions?: UIAction[];
58
+ }
59
+ export interface UIDetailField {
60
+ key: string;
61
+ label: string;
62
+ semantic?: "currency" | "count" | "ratio";
63
+ currency?: string;
64
+ }
65
+ export interface UIAggregateMetric {
66
+ key: string;
67
+ label: string;
68
+ authoritativeType: "number" | "integer";
69
+ semantic: "currency" | "count" | "ratio";
70
+ currency?: string;
71
+ derivation: {
72
+ source: "authoritative-output-schema";
73
+ reason: "metric-semantics";
74
+ };
75
+ }
76
+ export interface UIViewProvenance {
77
+ strategy: "deterministic" | "generative";
78
+ provider?: string;
79
+ model?: string;
80
+ capabilityVersion: string;
81
+ generatedAt: string;
82
+ requestedStrategy?: "deterministic" | "generative";
83
+ fallback?: boolean;
84
+ reason?: string;
85
+ source?: "generated" | "memory-cache" | "persistent-cache";
86
+ }
87
+ export interface UISuccessTransition {
88
+ capability: string;
89
+ refresh?: boolean;
90
+ resultBindings?: Record<string, string>;
91
+ }
92
+ export interface UIViewDerivation {
93
+ surface: {
94
+ source: "input-schema" | "output-schema";
95
+ reason: "no-output-schema" | "single-top-level-array-of-objects" | "flat-output-object" | "structured-output-object" | "authoritative-aggregate-object" | "authoritative-time-series" | "authoritative-category-series";
96
+ };
97
+ fields: "input-schema";
98
+ actions: "relationship-metadata";
99
+ confirmation?: "authoritative-risk";
100
+ successTransition?: "transition-metadata";
101
+ }
102
+ export interface CapabilityView {
103
+ capability: string;
104
+ title: string;
105
+ description: string;
106
+ risk: "read" | "write" | "destructive";
107
+ surface: "form" | "table" | "detail" | "aggregate" | "time-series" | "category-series";
108
+ fields: UIField[];
109
+ derivation: UIViewDerivation;
110
+ sections?: UISection[];
111
+ confirmation?: UIConfirmation;
112
+ table?: UITable;
113
+ detail?: {
114
+ fields: UIDetailField[];
115
+ actions?: UIAction[];
116
+ };
117
+ aggregate?: {
118
+ metrics: UIAggregateMetric[];
119
+ };
120
+ categorySeries?: {
121
+ dataPath: string;
122
+ visualization: "bar";
123
+ ordering: "source-order";
124
+ dimension: {
125
+ key: string;
126
+ label: string;
127
+ type: "string";
128
+ enum?: string[];
129
+ role: "dimension";
130
+ };
131
+ measure: {
132
+ key: string;
133
+ label: string;
134
+ type: "number" | "integer";
135
+ semantic: "count" | "currency" | "ratio";
136
+ currency?: string;
137
+ role: "measure";
138
+ };
139
+ derivation: {
140
+ source: "authoritative-output-schema";
141
+ reason: "categorical-dimension-with-numeric-measure";
142
+ };
143
+ };
144
+ timeSeries?: {
145
+ dataPath: string;
146
+ visualization: "line";
147
+ dimension: {
148
+ key: string;
149
+ label: string;
150
+ type: "string";
151
+ format: "date" | "date-time";
152
+ role: "dimension";
153
+ };
154
+ measure: {
155
+ key: string;
156
+ label: string;
157
+ type: "number" | "integer";
158
+ semantic: "count" | "currency" | "ratio";
159
+ currency?: string;
160
+ role: "measure";
161
+ };
162
+ derivation: {
163
+ source: "authoritative-output-schema";
164
+ reason: "temporal-dimension-with-numeric-measure";
165
+ };
166
+ };
167
+ provenance?: UIViewProvenance;
168
+ submitLabel: string;
169
+ onSuccess?: UISuccessTransition;
170
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import type { CapabilityView } from "./capability-view.js";
2
+ export interface SurfaceExecution {
3
+ capability: string;
4
+ view: CapabilityView;
5
+ result: Record<string, unknown>;
6
+ contextToken: string;
7
+ }
8
+ export interface RelatedSurfaceExecution {
9
+ capability: string;
10
+ label: string;
11
+ bindings: Record<string, string>;
12
+ resolvedInputs: Record<string, unknown>;
13
+ role: "related";
14
+ execution: "automatic-read";
15
+ reason: "authoritative-related-relationship-and-read-only-target";
16
+ status: "success" | "empty" | "failed";
17
+ view?: CapabilityView;
18
+ result?: Record<string, unknown>;
19
+ error?: string;
20
+ }
21
+ export type PresentationSurface = CapabilityView["surface"] | "error";
22
+ export interface PresentationEnvelopeSection {
23
+ id: string;
24
+ capability: string;
25
+ surface: PresentationSurface;
26
+ label: string;
27
+ required: true;
28
+ movable: boolean;
29
+ groupable: boolean;
30
+ defaultOrder: number;
31
+ }
32
+ export interface PresentationEnvelope {
33
+ applicationSurface: string;
34
+ sections: PresentationEnvelopeSection[];
35
+ immutable: string[];
36
+ }
37
+ export interface PresentationGroup {
38
+ id: string;
39
+ title?: string;
40
+ description?: string;
41
+ emphasis: "normal" | "prominent" | "subtle";
42
+ density: "comfortable" | "compact";
43
+ sections: string[];
44
+ }
45
+ export interface PresentationPlan {
46
+ title?: string;
47
+ description?: string;
48
+ groups: PresentationGroup[];
49
+ }
50
+ export interface PresentationProvenance {
51
+ strategy: "deterministic" | "generative";
52
+ requestedStrategy: "deterministic" | "generative";
53
+ provider?: string;
54
+ model?: string;
55
+ fallback: boolean;
56
+ validated: boolean;
57
+ reason?: string;
58
+ }
59
+ export interface ComposedSurface {
60
+ primary: SurfaceExecution;
61
+ related: RelatedSurfaceExecution[];
62
+ presentation: {
63
+ envelope: PresentationEnvelope;
64
+ plan: PresentationPlan;
65
+ provenance: PresentationProvenance;
66
+ };
67
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import type { CapabilityView } from "./capability-view.js";
2
+ import type { DerivedApplication, DerivedApplicationCapability } from "./application.js";
3
+ import type { CapabilityInteractionResolution } from "./interaction-resolution.js";
4
+ export interface InspectionEdge {
5
+ kind: "relationship" | "success";
6
+ source: string;
7
+ target: string;
8
+ label: string;
9
+ bindings: Record<string, string>;
10
+ conditions: string[];
11
+ }
12
+ export interface CompositionInspection {
13
+ application: {
14
+ id: string;
15
+ title: string;
16
+ entryCapability: string;
17
+ members: string[];
18
+ };
19
+ capabilities: Array<{
20
+ name: string;
21
+ entry: boolean;
22
+ risk: DerivedApplicationCapability["risk"];
23
+ roles: import("./experience-orchestration.js").ApplicationExperienceRole[];
24
+ }>;
25
+ edges: InspectionEdge[];
26
+ surface?: {
27
+ primary: string;
28
+ related: Array<{
29
+ capability: string;
30
+ label: string;
31
+ bindings: Record<string, string>;
32
+ execution: "automatic-read";
33
+ reason: string;
34
+ }>;
35
+ actions: Array<{
36
+ capability: string;
37
+ label: string;
38
+ bindings: Record<string, string>;
39
+ }>;
40
+ };
41
+ active?: {
42
+ capability: DerivedApplicationCapability;
43
+ view: CapabilityView;
44
+ interaction: CapabilityInteractionResolution;
45
+ };
46
+ recipe: Array<{
47
+ label: string;
48
+ metadata: unknown;
49
+ }>;
50
+ }
51
+ export declare function buildCompositionInspection(application: DerivedApplication, activeCapabilityName: string | null, view: CapabilityView | null, interaction: CapabilityInteractionResolution | null): CompositionInspection;
@@ -0,0 +1,102 @@
1
+ import { deriveApplicationExperience } from "./experience-orchestration.js";
2
+ import { interpretRelationship } from "./relationship-interpretation.js";
3
+ function conditionText(condition) {
4
+ return condition.operator === "exists"
5
+ ? `${condition.field} exists`
6
+ : `${condition.field} equals ${String(condition.value)}`;
7
+ }
8
+ export function buildCompositionInspection(application, activeCapabilityName, view, interaction) {
9
+ const edges = [];
10
+ const experience = deriveApplicationExperience(application);
11
+ for (const capability of application.capabilities) {
12
+ for (const relationship of capability.relationships) {
13
+ edges.push({
14
+ kind: "relationship",
15
+ source: capability.name,
16
+ target: relationship.targetCapability,
17
+ label: relationship.label,
18
+ bindings: relationship.bindings,
19
+ conditions: (relationship.conditions ?? []).map(conditionText),
20
+ });
21
+ }
22
+ if (capability.transition?.onSuccess) {
23
+ edges.push({
24
+ kind: "success",
25
+ source: capability.name,
26
+ target: capability.transition.onSuccess.capability,
27
+ label: "success",
28
+ bindings: capability.transition.onSuccess.resultBindings ?? {},
29
+ conditions: [],
30
+ });
31
+ }
32
+ }
33
+ const activeCapability = application.capabilities.find((capability) => capability.name === activeCapabilityName);
34
+ const recipe = [{
35
+ label: "Register capabilities and authoritative input schemas",
36
+ metadata: Object.fromEntries(application.capabilities.map((capability) => [
37
+ capability.name,
38
+ capability.inputSchema,
39
+ ])),
40
+ }];
41
+ if (application.capabilities.some((capability) => capability.outputSchema)) {
42
+ recipe.push({
43
+ label: "Define authoritative output schemas",
44
+ metadata: Object.fromEntries(application.capabilities
45
+ .filter((capability) => capability.outputSchema)
46
+ .map((capability) => [capability.name, capability.outputSchema])),
47
+ });
48
+ }
49
+ if (edges.some((edge) => edge.kind === "relationship")) {
50
+ recipe.push({
51
+ label: "Add relationships, bindings, and conditions",
52
+ metadata: edges.filter((edge) => edge.kind === "relationship"),
53
+ });
54
+ }
55
+ if (edges.some((edge) => edge.kind === "success")) {
56
+ recipe.push({
57
+ label: "Add success transitions and result bindings",
58
+ metadata: edges.filter((edge) => edge.kind === "success"),
59
+ });
60
+ }
61
+ recipe.push({
62
+ label: "Add an application manifest",
63
+ metadata: {
64
+ id: application.id,
65
+ entryCapability: application.entryCapability,
66
+ capabilities: application.capabilities.map((capability) => capability.name),
67
+ },
68
+ }, {
69
+ label: "Validate the derived application graph",
70
+ metadata: { applicationId: application.id },
71
+ }, {
72
+ label: "Render the derived view through the generic shell",
73
+ metadata: view
74
+ ? {
75
+ capability: view.capability,
76
+ surface: view.surface,
77
+ derivation: view.derivation,
78
+ }
79
+ : {},
80
+ });
81
+ const activeRelationships = activeCapability?.relationships ?? [];
82
+ return {
83
+ application: {
84
+ id: application.id,
85
+ title: application.title,
86
+ entryCapability: application.entryCapability,
87
+ members: application.capabilities.map((capability) => capability.name),
88
+ },
89
+ capabilities: application.capabilities.map((capability) => ({
90
+ name: capability.name,
91
+ entry: capability.name === application.entryCapability,
92
+ risk: capability.risk,
93
+ roles: experience.find((item) => item.capability.name === capability.name)?.roles ?? [],
94
+ })),
95
+ edges,
96
+ ...(activeCapability ? { surface: { primary: activeCapability.name, related: activeRelationships.filter((r) => interpretRelationship(r, "detail") === "related-read").map((r) => ({ capability: r.targetCapability, label: r.label, bindings: r.bindings, execution: "automatic-read", reason: "authoritative related relationship and read-only target" })), actions: activeRelationships.filter((r) => interpretRelationship(r, "detail") !== "related-read").map((r) => ({ capability: r.targetCapability, label: r.label, bindings: r.bindings })) } } : {}),
97
+ ...(activeCapability && view && interaction && view.capability === activeCapability.name
98
+ ? { active: { capability: activeCapability, view, interaction } }
99
+ : {}),
100
+ recipe,
101
+ };
102
+ }
@@ -0,0 +1,43 @@
1
+ import type { UIField } from "./capability-view.js";
2
+ import { type RichCapabilityInputSchema } from "./input-schema.js";
3
+ export interface ComponentNode {
4
+ component: string;
5
+ field?: string;
6
+ action?: string;
7
+ text?: string;
8
+ children?: ComponentNode[];
9
+ }
10
+ export interface ComposedApplication {
11
+ id: string;
12
+ title: string;
13
+ fields: RichCapabilityInputSchema;
14
+ initialState?: Record<string, unknown>;
15
+ screen: ComponentNode;
16
+ actions: readonly string[];
17
+ }
18
+ export type SubmissionStatus = "idle" | "submitting" | "success" | "error";
19
+ export interface CompositionSnapshot {
20
+ values: Record<string, unknown>;
21
+ errors: Record<string, string>;
22
+ status: SubmissionStatus;
23
+ message: string;
24
+ }
25
+ export type ActionResult = {
26
+ ok: true;
27
+ } | {
28
+ ok: false;
29
+ };
30
+ export type ActionHandler = (input: Readonly<Record<string, unknown>>) => Promise<ActionResult> | ActionResult;
31
+ export type ActionHandlers = Readonly<Record<string, ActionHandler>>;
32
+ export declare function defineComposedApplication<const T extends ComposedApplication>(definition: T): T;
33
+ export declare function validateComposedApplication(definition: ComposedApplication, customComponents?: readonly string[]): void;
34
+ export interface CompositionController<Action extends string = string, Field extends string = string> {
35
+ readonly definition: ComposedApplication;
36
+ snapshot(): CompositionSnapshot;
37
+ field(name: Field): UIField;
38
+ setField(name: Field, value: unknown): void;
39
+ submit(action: Action): Promise<boolean>;
40
+ subscribe(listener: (snapshot: CompositionSnapshot) => void): () => void;
41
+ dispose(): void;
42
+ }
43
+ export declare function createCompositionController<const T extends ComposedApplication>(definition: T, handlers: Readonly<Record<T["actions"][number], ActionHandler>>, customComponents?: readonly string[]): CompositionController<T["actions"][number], keyof T["fields"] & string>;