@you-agent-factory/client 0.0.0 → 0.0.1

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 (47) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +78 -17
  3. package/dist/contracts.d.ts +51 -0
  4. package/dist/contracts.js +9 -0
  5. package/dist/event-ordering.d.ts +15 -0
  6. package/dist/event-ordering.js +33 -0
  7. package/dist/generated/factory-event.schema.json +5490 -0
  8. package/dist/generated/factory.schema.json +2606 -0
  9. package/dist/generated/openapi.d.ts +7587 -0
  10. package/dist/generated/openapi.js +1020 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +8 -0
  13. package/dist/recording.d.ts +34 -0
  14. package/dist/recording.js +236 -0
  15. package/dist/replay.d.ts +21 -0
  16. package/dist/replay.js +82 -0
  17. package/dist/schema-validation.d.ts +9 -0
  18. package/dist/schema-validation.js +113 -0
  19. package/dist/visualization-layout-contracts.d.ts +63 -0
  20. package/dist/visualization-layout-contracts.js +1 -0
  21. package/dist/visualization-layout-data.d.ts +9 -0
  22. package/dist/visualization-layout-data.js +105 -0
  23. package/dist/visualization-layout-error.d.ts +5 -0
  24. package/dist/visualization-layout-error.js +10 -0
  25. package/dist/visualization-layout-fields.d.ts +7 -0
  26. package/dist/visualization-layout-fields.js +26 -0
  27. package/dist/visualization-layout-geometry.d.ts +5 -0
  28. package/dist/visualization-layout-geometry.js +74 -0
  29. package/dist/visualization-layout-media.d.ts +11 -0
  30. package/dist/visualization-layout-media.js +110 -0
  31. package/dist/visualization-layout-safety.d.ts +15 -0
  32. package/dist/visualization-layout-safety.js +142 -0
  33. package/dist/visualization-layout-topology.d.ts +11 -0
  34. package/dist/visualization-layout-topology.js +90 -0
  35. package/dist/visualization-layout.d.ts +12 -0
  36. package/dist/visualization-layout.js +299 -0
  37. package/examples/customer-support.factory-recording.v1.json +64 -0
  38. package/examples/customer-support.factory-visualization-layout.v1.json +47 -0
  39. package/package.json +37 -18
  40. package/recordings/factory-recording.json +0 -36
  41. package/src/contracts.ts +0 -13
  42. package/src/generated/factory-recording.schema.json +0 -5521
  43. package/src/generated/openapi.ts +0 -8134
  44. package/src/index.js +0 -5
  45. package/src/index.ts +0 -17
  46. package/src/recording-parser.d.ts +0 -37
  47. package/src/recording-parser.js +0 -230
@@ -0,0 +1,8 @@
1
+ export { type components, FACTORY_EVENT_SCHEMA_VERSIONS, FACTORY_EVENT_TYPES, type FactoryDefinition, type FactoryEvent, type FactoryEventType, type operations, type paths, } from "./contracts.js";
2
+ export { compareFactoryEvents, createFactoryEventCursor, type FactoryEventCursor, getFactoryEventEffectiveSequence, orderFactoryEvents, } from "./event-ordering.js";
3
+ export { FACTORY_RECORDING_SCHEMA_VERSION, type FactoryRecording, FactoryRecordingValidationError, parseFactoryRecording, type RecordingValidationIssue, type RecordingValidationIssueCode, type SafeParseFactoryRecordingResult, safeParseFactoryRecording, } from "./recording.js";
4
+ export { type FactoryReplayTextIssue, type FactoryReplayTextIssueCode, FactoryReplayTextParseError, parseFactoryEventReplayText, type SafeParseFactoryEventReplayTextResult, safeParseFactoryEventReplayText, } from "./replay.js";
5
+ export { parseFactoryVisualizationLayout, safeParseFactoryVisualizationLayout, type FactoryVisualizationLayoutCanonicalNodeContext, } from "./visualization-layout.js";
6
+ export { FACTORY_VISUALIZATION_LAYOUT_SCHEMA_VERSION, type FactoryVisualizationAnnotation, type FactoryVisualizationEmbeddedImageSource, type FactoryVisualizationImageAnnotation, type FactoryVisualizationImageContent, type FactoryVisualizationLayoutIssue, type FactoryVisualizationLayoutIssueCode, type FactoryVisualizationLayoutV1, type FactoryVisualizationNodeEmptyState, type FactoryVisualizationNoteAnnotation, type FactoryVisualizationNoteTone, type FactoryVisualizationPosition, type FactoryVisualizationSize, type FactoryVisualizationTextEmptyState, type SafeParseFactoryVisualizationLayoutResult, } from "./visualization-layout-contracts.js";
7
+ export { FactoryVisualizationLayoutValidationError } from "./visualization-layout-error.js";
8
+ export { MAX_EMBEDDED_IMAGE_BYTES, MAX_IMAGE_ALT_TEXT_LENGTH, MAX_LAYOUT_IMAGE_BYTES, } from "./visualization-layout-media.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { FACTORY_EVENT_SCHEMA_VERSIONS, FACTORY_EVENT_TYPES, } from "./contracts.js";
2
+ export { compareFactoryEvents, createFactoryEventCursor, getFactoryEventEffectiveSequence, orderFactoryEvents, } from "./event-ordering.js";
3
+ export { FACTORY_RECORDING_SCHEMA_VERSION, FactoryRecordingValidationError, parseFactoryRecording, safeParseFactoryRecording, } from "./recording.js";
4
+ export { FactoryReplayTextParseError, parseFactoryEventReplayText, safeParseFactoryEventReplayText, } from "./replay.js";
5
+ export { parseFactoryVisualizationLayout, safeParseFactoryVisualizationLayout, } from "./visualization-layout.js";
6
+ export { FACTORY_VISUALIZATION_LAYOUT_SCHEMA_VERSION, } from "./visualization-layout-contracts.js";
7
+ export { FactoryVisualizationLayoutValidationError } from "./visualization-layout-error.js";
8
+ export { MAX_EMBEDDED_IMAGE_BYTES, MAX_IMAGE_ALT_TEXT_LENGTH, MAX_LAYOUT_IMAGE_BYTES, } from "./visualization-layout-media.js";
@@ -0,0 +1,34 @@
1
+ import { type FactoryDefinition, type FactoryEvent } from "./contracts.js";
2
+ export declare const FACTORY_RECORDING_SCHEMA_VERSION: "factory-recording/v1";
3
+ export interface FactoryRecording {
4
+ schemaVersion: typeof FACTORY_RECORDING_SCHEMA_VERSION;
5
+ id: string;
6
+ title: string;
7
+ summary?: string;
8
+ factory?: FactoryDefinition;
9
+ events: FactoryEvent[];
10
+ }
11
+ export type RecordingValidationIssueCode = "invalid_type" | "invalid_value" | "missing_required_field" | "unsupported_field" | "unsupported_recording_schema_version" | "unsupported_event_type" | "unsupported_event_schema_version" | "duplicate_event_id" | "mixed_factory_session_identity" | "missing_topology_bootstrap";
12
+ export interface RecordingValidationIssue {
13
+ category: "structure" | "semantic";
14
+ code: RecordingValidationIssueCode;
15
+ path: readonly (string | number)[];
16
+ message: string;
17
+ }
18
+ export type SafeParseFactoryRecordingResult = {
19
+ success: true;
20
+ data: FactoryRecording;
21
+ } | {
22
+ success: false;
23
+ issues: readonly RecordingValidationIssue[];
24
+ };
25
+ export declare class FactoryRecordingValidationError extends Error {
26
+ readonly issues: readonly RecordingValidationIssue[];
27
+ constructor(issues: readonly RecordingValidationIssue[]);
28
+ }
29
+ /** Validate the shared canonical event envelope without applying recording-wide rules. */
30
+ export declare function validateFactoryEventEnvelope(input: unknown, path?: readonly (string | number)[]): RecordingValidationIssue[];
31
+ /** Validate the generated Factory event schema version for any input source. */
32
+ export declare function validateFactoryEventSchemaVersion(input: unknown, path?: readonly (string | number)[]): RecordingValidationIssue[];
33
+ export declare function safeParseFactoryRecording(input: unknown): SafeParseFactoryRecordingResult;
34
+ export declare function parseFactoryRecording(input: unknown): FactoryRecording;
@@ -0,0 +1,236 @@
1
+ import { FACTORY_EVENT_SCHEMA_VERSIONS, FACTORY_EVENT_TYPES, } from "./contracts.js";
2
+ import { orderFactoryEvents } from "./event-ordering.js";
3
+ import { canonicalEventIssues, canonicalFactoryIssues, } from "./schema-validation.js";
4
+ export const FACTORY_RECORDING_SCHEMA_VERSION = "factory-recording/v1";
5
+ export class FactoryRecordingValidationError extends Error {
6
+ issues;
7
+ constructor(issues) {
8
+ super(issues.length === 1
9
+ ? `Factory recording validation failed: ${issues[0]?.message}`
10
+ : `Factory recording validation failed with ${issues.length} issues`);
11
+ this.name = "FactoryRecordingValidationError";
12
+ this.issues = issues;
13
+ }
14
+ }
15
+ const supportedEventSchemaVersions = new Set(Object.values(FACTORY_EVENT_SCHEMA_VERSIONS));
16
+ const topologyEventTypes = new Set([
17
+ FACTORY_EVENT_TYPES.FactoryEventTypeRunRequest,
18
+ FACTORY_EVENT_TYPES.FactoryEventTypeInitialStructureRequest,
19
+ FACTORY_EVENT_TYPES.FactoryEventTypeFactoryChange,
20
+ ]);
21
+ const recordingFields = new Set([
22
+ "schemaVersion",
23
+ "id",
24
+ "title",
25
+ "summary",
26
+ "factory",
27
+ "events",
28
+ ]);
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ function addRequiredStringIssue(value, key, path, issues) {
33
+ if (!(key in value)) {
34
+ issues.push({
35
+ category: "structure",
36
+ code: "missing_required_field",
37
+ path: [...path, key],
38
+ message: `Expected required field ${key}.`,
39
+ });
40
+ }
41
+ else if (typeof value[key] !== "string") {
42
+ issues.push({
43
+ category: "structure",
44
+ code: "invalid_type",
45
+ path: [...path, key],
46
+ message: `Expected ${key} to be a string.`,
47
+ });
48
+ }
49
+ }
50
+ function validateFactoryDefinition(input, path, issues) {
51
+ issues.push(...canonicalFactoryIssues(input, path));
52
+ }
53
+ /** Validate the shared canonical event envelope without applying recording-wide rules. */
54
+ export function validateFactoryEventEnvelope(input, path = []) {
55
+ return canonicalEventIssues(input, path);
56
+ }
57
+ function hasUsableFactoryDefinition(input) {
58
+ return (isRecord(input) &&
59
+ typeof input.name === "string" &&
60
+ input.name.trim().length > 0);
61
+ }
62
+ function validateEventSchemaVersions(events, issues) {
63
+ for (const [index, event] of events.entries()) {
64
+ issues.push(...validateFactoryEventSchemaVersion(event, ["events", index]));
65
+ }
66
+ }
67
+ /** Validate the generated Factory event schema version for any input source. */
68
+ export function validateFactoryEventSchemaVersion(input, path = []) {
69
+ if (!isRecord(input) ||
70
+ typeof input.schemaVersion !== "string" ||
71
+ supportedEventSchemaVersions.has(input.schemaVersion)) {
72
+ return [];
73
+ }
74
+ return [
75
+ {
76
+ category: "semantic",
77
+ code: "unsupported_event_schema_version",
78
+ path: [...path, "schemaVersion"],
79
+ message: `Unsupported Factory event schema version: ${input.schemaVersion}.`,
80
+ },
81
+ ];
82
+ }
83
+ function validateUniqueEventIds(events, issues) {
84
+ const locationsById = new Map();
85
+ for (const [index, event] of events.entries()) {
86
+ const locations = locationsById.get(event.id) ?? [];
87
+ locations.push(index);
88
+ locationsById.set(event.id, locations);
89
+ }
90
+ for (const [eventId, locations] of locationsById) {
91
+ if (locations.length < 2) {
92
+ continue;
93
+ }
94
+ const locationList = locations.join(", ");
95
+ for (const index of locations) {
96
+ issues.push({
97
+ category: "semantic",
98
+ code: "duplicate_event_id",
99
+ path: ["events", index, "id"],
100
+ message: `Event ID ${eventId} is duplicated at event indexes ${locationList}.`,
101
+ });
102
+ }
103
+ }
104
+ }
105
+ function validateFactorySessionIdentity(events, issues) {
106
+ const sessionIds = events.map((event) => isRecord(event.context) && typeof event.context.sessionId === "string"
107
+ ? event.context.sessionId
108
+ : undefined);
109
+ const presentSessionIds = new Set(sessionIds.filter((sessionId) => sessionId !== undefined));
110
+ const identitiesAreMixed = presentSessionIds.size > 1 ||
111
+ (presentSessionIds.size === 1 && sessionIds.some((id) => id === undefined));
112
+ if (!identitiesAreMixed) {
113
+ return;
114
+ }
115
+ for (const [index, sessionId] of sessionIds.entries()) {
116
+ issues.push({
117
+ category: "semantic",
118
+ code: "mixed_factory_session_identity",
119
+ path: ["events", index, "context", "sessionId"],
120
+ message: sessionId === undefined
121
+ ? "Expected a Factory Session ID consistent with the other recording events."
122
+ : `Factory Session ID ${sessionId} is inconsistent with the recording event history.`,
123
+ });
124
+ }
125
+ }
126
+ function validateTopologyBootstrap(factory, events, issues) {
127
+ const hasTopologyEvent = events.some((event) => typeof event.type === "string" &&
128
+ topologyEventTypes.has(event.type) &&
129
+ isRecord(event.payload) &&
130
+ hasUsableFactoryDefinition(event.payload.factory));
131
+ if (!hasUsableFactoryDefinition(factory) && !hasTopologyEvent) {
132
+ issues.push({
133
+ category: "semantic",
134
+ code: "missing_topology_bootstrap",
135
+ path: ["events"],
136
+ message: "Expected a usable top-level factory or a topology-establishing Factory event.",
137
+ });
138
+ }
139
+ }
140
+ function validateRecordingSemantics(input, events) {
141
+ const issues = [];
142
+ validateEventSchemaVersions(events, issues);
143
+ validateUniqueEventIds(events, issues);
144
+ validateFactorySessionIdentity(events, issues);
145
+ validateTopologyBootstrap(input.factory, events, issues);
146
+ return issues;
147
+ }
148
+ export function safeParseFactoryRecording(input) {
149
+ if (!isRecord(input)) {
150
+ return {
151
+ success: false,
152
+ issues: [
153
+ {
154
+ category: "structure",
155
+ code: "invalid_type",
156
+ path: [],
157
+ message: "Expected Factory recording to be an object.",
158
+ },
159
+ ],
160
+ };
161
+ }
162
+ const issues = [];
163
+ for (const key of Object.keys(input)) {
164
+ if (!recordingFields.has(key)) {
165
+ issues.push({
166
+ category: "structure",
167
+ code: "unsupported_field",
168
+ path: [key],
169
+ message: `Unsupported field ${key}.`,
170
+ });
171
+ }
172
+ }
173
+ addRequiredStringIssue(input, "schemaVersion", [], issues);
174
+ addRequiredStringIssue(input, "id", [], issues);
175
+ addRequiredStringIssue(input, "title", [], issues);
176
+ if (typeof input.schemaVersion === "string" &&
177
+ input.schemaVersion !== FACTORY_RECORDING_SCHEMA_VERSION) {
178
+ issues.push({
179
+ category: "structure",
180
+ code: "unsupported_recording_schema_version",
181
+ path: ["schemaVersion"],
182
+ message: `Unsupported Factory recording schema version: ${input.schemaVersion}.`,
183
+ });
184
+ }
185
+ if (input.summary !== undefined && typeof input.summary !== "string") {
186
+ issues.push({
187
+ category: "structure",
188
+ code: "invalid_type",
189
+ path: ["summary"],
190
+ message: "Expected summary to be a string when present.",
191
+ });
192
+ }
193
+ if (input.factory !== undefined) {
194
+ validateFactoryDefinition(input.factory, ["factory"], issues);
195
+ }
196
+ if (!("events" in input)) {
197
+ issues.push({
198
+ category: "structure",
199
+ code: "missing_required_field",
200
+ path: ["events"],
201
+ message: "Expected required field events.",
202
+ });
203
+ }
204
+ else if (!Array.isArray(input.events)) {
205
+ issues.push({
206
+ category: "structure",
207
+ code: "invalid_type",
208
+ path: ["events"],
209
+ message: "Expected events to be an array.",
210
+ });
211
+ }
212
+ else {
213
+ for (const [index, event] of input.events.entries()) {
214
+ issues.push(...validateFactoryEventEnvelope(event, ["events", index]));
215
+ }
216
+ }
217
+ if (issues.length === 0 && Array.isArray(input.events)) {
218
+ issues.push(...validateRecordingSemantics(input, input.events));
219
+ }
220
+ return issues.length > 0
221
+ ? { success: false, issues }
222
+ : {
223
+ success: true,
224
+ data: {
225
+ ...input,
226
+ events: orderFactoryEvents(input.events),
227
+ },
228
+ };
229
+ }
230
+ export function parseFactoryRecording(input) {
231
+ const result = safeParseFactoryRecording(input);
232
+ if (!result.success) {
233
+ throw new FactoryRecordingValidationError(result.issues);
234
+ }
235
+ return result.data;
236
+ }
@@ -0,0 +1,21 @@
1
+ import type { FactoryEvent } from "./contracts.js";
2
+ import { type RecordingValidationIssueCode } from "./recording.js";
3
+ export type FactoryReplayTextIssueCode = "malformed_event_json" | RecordingValidationIssueCode;
4
+ export interface FactoryReplayTextIssue {
5
+ code: FactoryReplayTextIssueCode;
6
+ path: readonly (string | number)[];
7
+ message: string;
8
+ }
9
+ export type SafeParseFactoryEventReplayTextResult = {
10
+ success: true;
11
+ data: FactoryEvent[];
12
+ } | {
13
+ success: false;
14
+ issues: readonly FactoryReplayTextIssue[];
15
+ };
16
+ export declare class FactoryReplayTextParseError extends Error {
17
+ readonly issues: readonly FactoryReplayTextIssue[];
18
+ constructor(issues: readonly FactoryReplayTextIssue[]);
19
+ }
20
+ export declare function safeParseFactoryEventReplayText(replayText: string): SafeParseFactoryEventReplayTextResult;
21
+ export declare function parseFactoryEventReplayText(replayText: string): FactoryEvent[];
package/dist/replay.js ADDED
@@ -0,0 +1,82 @@
1
+ import { orderFactoryEvents } from "./event-ordering.js";
2
+ import { validateFactoryEventEnvelope, validateFactoryEventSchemaVersion, } from "./recording.js";
3
+ export class FactoryReplayTextParseError extends Error {
4
+ issues;
5
+ constructor(issues) {
6
+ super(issues.length === 1
7
+ ? `Factory replay text parsing failed: ${issues[0]?.message}`
8
+ : `Factory replay text parsing failed with ${issues.length} issues`);
9
+ this.name = "FactoryReplayTextParseError";
10
+ this.issues = issues;
11
+ }
12
+ }
13
+ function dataFrames(replayText) {
14
+ const normalizedText = replayText
15
+ .replaceAll("\r\n", "\n")
16
+ .replaceAll("\r", "\n");
17
+ const frames = [];
18
+ for (const block of normalizedText.split(/\n[\t ]*\n/)) {
19
+ const dataLines = [];
20
+ for (const line of block.split("\n")) {
21
+ if (line.startsWith(":")) {
22
+ continue;
23
+ }
24
+ const separator = line.indexOf(":");
25
+ const field = separator === -1 ? line : line.slice(0, separator);
26
+ if (field !== "data") {
27
+ continue;
28
+ }
29
+ let value = separator === -1 ? "" : line.slice(separator + 1);
30
+ if (value.startsWith(" ")) {
31
+ value = value.slice(1);
32
+ }
33
+ dataLines.push(value);
34
+ }
35
+ if (dataLines.length > 0) {
36
+ frames.push(dataLines.join("\n"));
37
+ }
38
+ }
39
+ return frames;
40
+ }
41
+ export function safeParseFactoryEventReplayText(replayText) {
42
+ const events = [];
43
+ const issues = [];
44
+ for (const [frameIndex, data] of dataFrames(replayText).entries()) {
45
+ let input;
46
+ try {
47
+ input = JSON.parse(data);
48
+ }
49
+ catch {
50
+ issues.push({
51
+ code: "malformed_event_json",
52
+ path: ["frames", frameIndex, "data"],
53
+ message: "Expected SSE data to contain valid Factory event JSON.",
54
+ });
55
+ continue;
56
+ }
57
+ const framePath = ["frames", frameIndex, "data"];
58
+ const validationIssues = [
59
+ ...validateFactoryEventEnvelope(input, framePath),
60
+ ...validateFactoryEventSchemaVersion(input, framePath),
61
+ ];
62
+ if (validationIssues.length > 0) {
63
+ issues.push(...validationIssues.map(({ code, path, message }) => ({
64
+ code,
65
+ path,
66
+ message,
67
+ })));
68
+ continue;
69
+ }
70
+ events.push(input);
71
+ }
72
+ return issues.length > 0
73
+ ? { success: false, issues }
74
+ : { success: true, data: orderFactoryEvents(events) };
75
+ }
76
+ export function parseFactoryEventReplayText(replayText) {
77
+ const result = safeParseFactoryEventReplayText(replayText);
78
+ if (!result.success) {
79
+ throw new FactoryReplayTextParseError(result.issues);
80
+ }
81
+ return result.data;
82
+ }
@@ -0,0 +1,9 @@
1
+ export type CanonicalSchemaIssueCode = "invalid_type" | "invalid_value" | "missing_required_field" | "unsupported_event_type" | "unsupported_field";
2
+ export interface CanonicalSchemaIssue {
3
+ category: "structure";
4
+ code: CanonicalSchemaIssueCode;
5
+ path: readonly (string | number)[];
6
+ message: string;
7
+ }
8
+ export declare function canonicalFactoryIssues(input: unknown, path: readonly (string | number)[]): CanonicalSchemaIssue[];
9
+ export declare function canonicalEventIssues(input: unknown, path: readonly (string | number)[]): CanonicalSchemaIssue[];
@@ -0,0 +1,113 @@
1
+ import Ajv2020, {} from "ajv/dist/2020.js";
2
+ import addFormats from "ajv-formats";
3
+ import factorySchema from "./generated/factory.schema.json" with {
4
+ type: "json"
5
+ };
6
+ import factoryEventSchema from "./generated/factory-event.schema.json" with {
7
+ type: "json"
8
+ };
9
+ const ajv = new Ajv2020({
10
+ allErrors: true,
11
+ coerceTypes: false,
12
+ removeAdditional: false,
13
+ strict: false,
14
+ useDefaults: false,
15
+ });
16
+ addFormats(ajv);
17
+ const canonicalEventSchema = factoryEventSchema;
18
+ const { $id: _eventSchemaId, discriminator: _eventDiscriminator, oneOf: eventVariants = [], ...eventEnvelopeSchema } = canonicalEventSchema;
19
+ const validateEventEnvelopeShape = ajv.compile(eventEnvelopeSchema);
20
+ const validateFactoryShape = ajv.compile(factorySchema);
21
+ const eventValidators = new Map();
22
+ function isRecord(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ for (const variant of eventVariants) {
26
+ const variantProperties = variant.properties;
27
+ const typeSchemaInput = isRecord(variantProperties)
28
+ ? variantProperties.type
29
+ : undefined;
30
+ const typeSchema = isRecord(typeSchemaInput) ? typeSchemaInput : undefined;
31
+ const eventType = typeSchema?.const;
32
+ if (typeof eventType !== "string") {
33
+ continue;
34
+ }
35
+ eventValidators.set(eventType, ajv.compile({
36
+ ...eventEnvelopeSchema,
37
+ properties: {
38
+ ...eventEnvelopeSchema.properties,
39
+ ...(isRecord(variantProperties) ? variantProperties : {}),
40
+ },
41
+ }));
42
+ }
43
+ function pointerSegments(pointer) {
44
+ if (pointer.length === 0) {
45
+ return [];
46
+ }
47
+ return pointer
48
+ .slice(1)
49
+ .split("/")
50
+ .map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
51
+ }
52
+ function issuePath(error, path) {
53
+ const segments = pointerSegments(error.instancePath).map((segment) => (/^(0|[1-9]\d*)$/.test(segment) ? Number(segment) : segment));
54
+ const property = error.keyword === "required"
55
+ ? error.params.missingProperty
56
+ : error.keyword === "additionalProperties"
57
+ ? error.params.additionalProperty
58
+ : undefined;
59
+ return typeof property === "string"
60
+ ? [...path, ...segments, property]
61
+ : [...path, ...segments];
62
+ }
63
+ function issueCode(error) {
64
+ if (error.keyword === "required")
65
+ return "missing_required_field";
66
+ if (error.keyword === "additionalProperties")
67
+ return "unsupported_field";
68
+ if (error.keyword === "type")
69
+ return "invalid_type";
70
+ if (error.keyword === "enum" && error.instancePath === "/type") {
71
+ return "unsupported_event_type";
72
+ }
73
+ return "invalid_value";
74
+ }
75
+ function issues(errors, path) {
76
+ return (errors ?? [])
77
+ .filter((error) => !(error.keyword === "enum" && error.instancePath === "/schemaVersion"))
78
+ .map((error) => {
79
+ const pathToIssue = issuePath(error, path);
80
+ const field = pathToIssue.at(-1);
81
+ return {
82
+ category: "structure",
83
+ code: issueCode(error),
84
+ path: pathToIssue,
85
+ message: error.keyword === "required"
86
+ ? `Expected required field ${String(field)}.`
87
+ : error.keyword === "additionalProperties"
88
+ ? `Unsupported field ${String(field)}.`
89
+ : `Expected ${pathToIssue.join(".") || "value"} to satisfy the canonical contract: ${error.message ?? error.keyword}.`,
90
+ };
91
+ });
92
+ }
93
+ export function canonicalFactoryIssues(input, path) {
94
+ return validateFactoryShape(input)
95
+ ? []
96
+ : issues(validateFactoryShape.errors, path);
97
+ }
98
+ export function canonicalEventIssues(input, path) {
99
+ if (!isRecord(input)) {
100
+ return [
101
+ {
102
+ category: "structure",
103
+ code: "invalid_type",
104
+ path,
105
+ message: "Expected Factory event to be an object.",
106
+ },
107
+ ];
108
+ }
109
+ const validator = typeof input.type === "string"
110
+ ? (eventValidators.get(input.type) ?? validateEventEnvelopeShape)
111
+ : validateEventEnvelopeShape;
112
+ return validator(input) ? [] : issues(validator.errors, path);
113
+ }
@@ -0,0 +1,63 @@
1
+ export declare const FACTORY_VISUALIZATION_LAYOUT_SCHEMA_VERSION: "factory-visualization-layout/v1";
2
+ export type FactoryVisualizationNoteTone = "neutral" | "accent" | "info" | "success" | "warning" | "danger";
3
+ export interface FactoryVisualizationPosition {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export interface FactoryVisualizationSize {
8
+ width: number;
9
+ height: number;
10
+ }
11
+ export interface FactoryVisualizationEmbeddedImageSource {
12
+ kind: "embedded";
13
+ mediaType: "image/png" | "image/jpeg" | "image/webp";
14
+ base64: string;
15
+ }
16
+ export interface FactoryVisualizationImageContent {
17
+ kind: "image";
18
+ altText: string;
19
+ source: FactoryVisualizationEmbeddedImageSource;
20
+ }
21
+ export interface FactoryVisualizationNoteAnnotation {
22
+ id: string;
23
+ kind: "note";
24
+ position: FactoryVisualizationPosition;
25
+ size?: FactoryVisualizationSize;
26
+ title?: string;
27
+ body: string;
28
+ tone?: FactoryVisualizationNoteTone;
29
+ }
30
+ export interface FactoryVisualizationImageAnnotation extends FactoryVisualizationImageContent {
31
+ id: string;
32
+ position: FactoryVisualizationPosition;
33
+ size: FactoryVisualizationSize;
34
+ }
35
+ export type FactoryVisualizationAnnotation = FactoryVisualizationNoteAnnotation | FactoryVisualizationImageAnnotation;
36
+ export interface FactoryVisualizationTextEmptyState {
37
+ kind: "text";
38
+ text: string;
39
+ }
40
+ export interface FactoryVisualizationNodeEmptyState {
41
+ nodeId: string;
42
+ content: FactoryVisualizationTextEmptyState | FactoryVisualizationImageContent;
43
+ }
44
+ /** Presentation-only metadata carried beside, never inside, a canonical Factory. */
45
+ export interface FactoryVisualizationLayoutV1 {
46
+ schemaVersion: typeof FACTORY_VISUALIZATION_LAYOUT_SCHEMA_VERSION;
47
+ annotations?: FactoryVisualizationAnnotation[];
48
+ nodeEmptyStates?: FactoryVisualizationNodeEmptyState[];
49
+ }
50
+ export type FactoryVisualizationLayoutIssueCode = "invalid_type" | "non_plain_data" | "invalid_value" | "missing_required_field" | "unsupported_field" | "unsupported_layout_schema_version" | "invalid_annotation_kind" | "invalid_empty_state_kind" | "duplicate_annotation_id" | "duplicate_node_id" | "empty_node_id" | "unknown_canonical_node_id" | "empty_text" | "text_too_long" | "unsafe_html" | "unsafe_markdown" | "unsafe_uri" | "unsafe_content_field" | "unsupported_note_tone" | "invalid_coordinate" | "invalid_dimension" | "invalid_base64" | "empty_image_payload" | "unsupported_image_media_type" | "image_media_type_mismatch" | "image_too_large" | "aggregate_image_bytes_exceeded";
51
+ export interface FactoryVisualizationLayoutIssue {
52
+ category: "structure" | "semantic";
53
+ code: FactoryVisualizationLayoutIssueCode;
54
+ path: readonly (string | number)[];
55
+ message: string;
56
+ }
57
+ export type SafeParseFactoryVisualizationLayoutResult = {
58
+ success: true;
59
+ data: FactoryVisualizationLayoutV1;
60
+ } | {
61
+ success: false;
62
+ issues: readonly FactoryVisualizationLayoutIssue[];
63
+ };
@@ -0,0 +1 @@
1
+ export const FACTORY_VISUALIZATION_LAYOUT_SCHEMA_VERSION = "factory-visualization-layout/v1";
@@ -0,0 +1,9 @@
1
+ import type { FactoryVisualizationLayoutIssue } from "./visualization-layout-contracts.js";
2
+ type InputPath = readonly (string | number)[];
3
+ export type InputRecord = Record<string, unknown>;
4
+ export declare function isInputRecord(value: unknown): value is InputRecord;
5
+ /** Reject behavior-bearing containers before the parser reads any input value. */
6
+ export declare function validatePlainDataContainers(value: unknown, path: InputPath, issues: FactoryVisualizationLayoutIssue[]): void;
7
+ /** Copy validated values into fresh standard containers without prototype data. */
8
+ export declare function clonePlainData(value: unknown): unknown;
9
+ export {};