dsh-knowhub 0.9.0 → 0.10.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 (195) hide show
  1. package/README.md +11 -2
  2. package/cordis.patch.yml +10 -0
  3. package/lib/host/model.js +1 -1
  4. package/lib/host/pipeline/incremental.js +3 -3
  5. package/lib/host/pipeline/initialize.js +144 -27
  6. package/lib/host/pipeline/model-output.js +2 -2
  7. package/package.json +3 -2
  8. package/skills/archify/LICENSE +202 -0
  9. package/skills/archify/LICENSE-MIT +22 -0
  10. package/skills/archify/NOTICE +26 -0
  11. package/skills/archify/SKILL.md +137 -0
  12. package/skills/archify/THIRD_PARTY_NOTICES.md +78 -0
  13. package/skills/archify/assets/JetBrainsMono-OFL.txt +93 -0
  14. package/skills/archify/assets/template.html +14768 -0
  15. package/skills/archify/bin/archify.mjs +7289 -0
  16. package/skills/archify/bin/delivery-update.mjs +92 -0
  17. package/skills/archify/bin/finalize.mjs +1055 -0
  18. package/skills/archify/bin/open-artifact.mjs +146 -0
  19. package/skills/archify/bin/preview.mjs +970 -0
  20. package/skills/archify/bin/recover-output.mjs +41 -0
  21. package/skills/archify/bin/visual-check.mjs +2780 -0
  22. package/skills/archify/brand-marks/README.md +31 -0
  23. package/skills/archify/brand-marks/catalog.json +131 -0
  24. package/skills/archify/delta/architecture-delta.mjs +1318 -0
  25. package/skills/archify/examples/agent-run.lifecycle.json +55 -0
  26. package/skills/archify/examples/agent-tool-call.workflow.json +74 -0
  27. package/skills/archify/examples/archify-dev-activity.timeline.json +176 -0
  28. package/skills/archify/examples/archify-repository.tree.json +197 -0
  29. package/skills/archify/examples/async-job-roundtrip.sequence.json +56 -0
  30. package/skills/archify/examples/brand-aware-delivery.architecture.json +34 -0
  31. package/skills/archify/examples/cache-miss-request.sequence.json +77 -0
  32. package/skills/archify/examples/checkout-platform.base.architecture.json +32 -0
  33. package/skills/archify/examples/checkout-platform.head.architecture.json +32 -0
  34. package/skills/archify/examples/checkout-request.waterfall.json +20 -0
  35. package/skills/archify/examples/delivery.explainer.json +155 -0
  36. package/skills/archify/examples/deployment-release.lifecycle.json +72 -0
  37. package/skills/archify/examples/event-stream.dataflow.json +52 -0
  38. package/skills/archify/examples/example-rebuild.waterfall.json +212 -0
  39. package/skills/archify/examples/holiday-planning.workflow.json +143 -0
  40. package/skills/archify/examples/incident-response.workflow.json +59 -0
  41. package/skills/archify/examples/locales/de.json +463 -0
  42. package/skills/archify/examples/locales/fr.json +463 -0
  43. package/skills/archify/examples/locales/fr.partial.json +22 -0
  44. package/skills/archify/examples/locales/it.json +463 -0
  45. package/skills/archify/examples/locales/ja.json +463 -0
  46. package/skills/archify/examples/locales/pt.json +463 -0
  47. package/skills/archify/examples/locales/ru.json +463 -0
  48. package/skills/archify/examples/orders.erd.json +337 -0
  49. package/skills/archify/examples/payment-incident.timeline.json +24 -0
  50. package/skills/archify/examples/payment-platform.tree.json +22 -0
  51. package/skills/archify/examples/payment-processors.class.json +177 -0
  52. package/skills/archify/examples/payments.class.json +85 -0
  53. package/skills/archify/examples/product-analytics.dataflow.json +71 -0
  54. package/skills/archify/examples/production-deployment.architecture.json +51 -0
  55. package/skills/archify/examples/release-delivery.workflow.json +57 -0
  56. package/skills/archify/examples/subscription-billing.erd.json +548 -0
  57. package/skills/archify/examples/web-app.architecture.json +41 -0
  58. package/skills/archify/explainer/browser-check.mjs +208 -0
  59. package/skills/archify/explainer/charts.mjs +70 -0
  60. package/skills/archify/explainer/check.mjs +167 -0
  61. package/skills/archify/explainer/diagrams.mjs +517 -0
  62. package/skills/archify/explainer/diff.mjs +124 -0
  63. package/skills/archify/explainer/inline.mjs +155 -0
  64. package/skills/archify/explainer/layout.mjs +35 -0
  65. package/skills/archify/explainer/page-bundle.mjs +16 -0
  66. package/skills/archify/explainer/parse.mjs +300 -0
  67. package/skills/archify/explainer/patch.mjs +117 -0
  68. package/skills/archify/explainer/prepare.mjs +131 -0
  69. package/skills/archify/explainer/reader.css +472 -0
  70. package/skills/archify/explainer/reader.js +666 -0
  71. package/skills/archify/explainer/render.mjs +224 -0
  72. package/skills/archify/explainer/revision.mjs +20 -0
  73. package/skills/archify/explainer/sources.mjs +318 -0
  74. package/skills/archify/explainer/validate.mjs +138 -0
  75. package/skills/archify/explainer/vendor/pele/LICENSE +40 -0
  76. package/skills/archify/explainer/vendor/pele/README.md +5 -0
  77. package/skills/archify/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
  78. package/skills/archify/explainer/vendor/pele/chunks/42UTT654.js +3 -0
  79. package/skills/archify/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
  80. package/skills/archify/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
  81. package/skills/archify/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
  82. package/skills/archify/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
  83. package/skills/archify/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
  84. package/skills/archify/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
  85. package/skills/archify/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
  86. package/skills/archify/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
  87. package/skills/archify/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
  88. package/skills/archify/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
  89. package/skills/archify/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
  90. package/skills/archify/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
  91. package/skills/archify/explainer/vendor/pele/core.js +1 -0
  92. package/skills/archify/explainer/vendor/pele/diagrams/pie.js +1 -0
  93. package/skills/archify/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
  94. package/skills/archify/explainer/vendor/pele/diagrams/radar.js +1 -0
  95. package/skills/archify/explainer/vendor/pele/diagrams/sankey.js +1 -0
  96. package/skills/archify/explainer/vendor/pele/diagrams/venn.js +1 -0
  97. package/skills/archify/explainer/vendor/pele/diagrams/xychart.js +1 -0
  98. package/skills/archify/explainer/writing.mjs +139 -0
  99. package/skills/archify/locales/README.md +40 -0
  100. package/skills/archify/locales/ar.json +488 -0
  101. package/skills/archify/locales/en.json +488 -0
  102. package/skills/archify/locales/es.json +488 -0
  103. package/skills/archify/locales/ko.json +488 -0
  104. package/skills/archify/locales/manifest.json +10 -0
  105. package/skills/archify/locales/zh-CN.json +488 -0
  106. package/skills/archify/locales/zh-TW.json +488 -0
  107. package/skills/archify/migrations/workflow-v2.mjs +280 -0
  108. package/skills/archify/package.json +17 -0
  109. package/skills/archify/recipes/scenarios.mjs +455 -0
  110. package/skills/archify/references/architecture-layout-repair.md +18 -0
  111. package/skills/archify/references/authoring-contract.md +645 -0
  112. package/skills/archify/references/authoring-defaults.md +57 -0
  113. package/skills/archify/references/brand-marks.md +119 -0
  114. package/skills/archify/references/delivery-contract.md +665 -0
  115. package/skills/archify/references/explainer-authoring.md +274 -0
  116. package/skills/archify/references/repository-authoring.md +126 -0
  117. package/skills/archify/references/update-awareness.md +13 -0
  118. package/skills/archify/references/viewer-runtime.md +52 -0
  119. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  120. package/skills/archify/renderers/architecture/labels.mjs +2 -0
  121. package/skills/archify/renderers/architecture/render-architecture.mjs +1204 -0
  122. package/skills/archify/renderers/architecture/routing.mjs +1469 -0
  123. package/skills/archify/renderers/class/README.md +130 -0
  124. package/skills/archify/renderers/class/render-class.mjs +1095 -0
  125. package/skills/archify/renderers/dataflow/README.md +176 -0
  126. package/skills/archify/renderers/dataflow/dataflow-compiler.mjs +1605 -0
  127. package/skills/archify/renderers/dataflow/joint-label-routing.mjs +165 -0
  128. package/skills/archify/renderers/dataflow/label-capacity.mjs +81 -0
  129. package/skills/archify/renderers/dataflow/render-dataflow.mjs +23 -0
  130. package/skills/archify/renderers/erd/README.md +246 -0
  131. package/skills/archify/renderers/erd/grid.mjs +150 -0
  132. package/skills/archify/renderers/erd/render-erd.mjs +1588 -0
  133. package/skills/archify/renderers/explainer/render-explainer.mjs +42 -0
  134. package/skills/archify/renderers/lifecycle/README.md +104 -0
  135. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +1134 -0
  136. package/skills/archify/renderers/sequence/README.md +146 -0
  137. package/skills/archify/renderers/sequence/render-sequence.mjs +1105 -0
  138. package/skills/archify/renderers/shared/atomic-output.mjs +1982 -0
  139. package/skills/archify/renderers/shared/automatic-labels.mjs +290 -0
  140. package/skills/archify/renderers/shared/brand-marks.mjs +706 -0
  141. package/skills/archify/renderers/shared/cli.mjs +430 -0
  142. package/skills/archify/renderers/shared/desktop-readability.mjs +132 -0
  143. package/skills/archify/renderers/shared/diagnostics.mjs +0 -0
  144. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  145. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  146. package/skills/archify/renderers/shared/generated-validators.mjs +279 -0
  147. package/skills/archify/renderers/shared/geometry.mjs +2073 -0
  148. package/skills/archify/renderers/shared/i18n.mjs +328 -0
  149. package/skills/archify/renderers/shared/layout-report.mjs +57 -0
  150. package/skills/archify/renderers/shared/legend.mjs +227 -0
  151. package/skills/archify/renderers/shared/node-card-style.mjs +32 -0
  152. package/skills/archify/renderers/shared/output-path.mjs +440 -0
  153. package/skills/archify/renderers/shared/owned-temp-directory.mjs +82 -0
  154. package/skills/archify/renderers/shared/path-semantics.mjs +1017 -0
  155. package/skills/archify/renderers/shared/portable-path.mjs +408 -0
  156. package/skills/archify/renderers/shared/repository-evidence.mjs +394 -0
  157. package/skills/archify/renderers/shared/repository-location.mjs +71 -0
  158. package/skills/archify/renderers/shared/route-quality.mjs +670 -0
  159. package/skills/archify/renderers/shared/sidecar-path.mjs +38 -0
  160. package/skills/archify/renderers/shared/software-notices.mjs +6 -0
  161. package/skills/archify/renderers/shared/spatial-grid.mjs +66 -0
  162. package/skills/archify/renderers/shared/svg-path-analysis.mjs +135 -0
  163. package/skills/archify/renderers/shared/text-fit.mjs +105 -0
  164. package/skills/archify/renderers/shared/utils.mjs +260 -0
  165. package/skills/archify/renderers/shared/validator.mjs +83 -0
  166. package/skills/archify/renderers/timeline/README.md +76 -0
  167. package/skills/archify/renderers/timeline/render-timeline.mjs +576 -0
  168. package/skills/archify/renderers/tree/README.md +96 -0
  169. package/skills/archify/renderers/tree/render-tree.mjs +573 -0
  170. package/skills/archify/renderers/waterfall/README.md +73 -0
  171. package/skills/archify/renderers/waterfall/render-waterfall.mjs +413 -0
  172. package/skills/archify/renderers/workflow/README.md +337 -0
  173. package/skills/archify/renderers/workflow/render-workflow.mjs +47 -0
  174. package/skills/archify/renderers/workflow/workflow-compiler.mjs +5920 -0
  175. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +161 -0
  176. package/skills/archify/renderers/workflow/workflow-placement-repair.mjs +94 -0
  177. package/skills/archify/schemas/README.md +258 -0
  178. package/skills/archify/schemas/architecture.schema.json +152 -0
  179. package/skills/archify/schemas/class.schema.json +345 -0
  180. package/skills/archify/schemas/common.schema.json +181 -0
  181. package/skills/archify/schemas/dataflow.schema.json +254 -0
  182. package/skills/archify/schemas/erd.schema.json +381 -0
  183. package/skills/archify/schemas/explainer.schema.json +927 -0
  184. package/skills/archify/schemas/lifecycle.schema.json +179 -0
  185. package/skills/archify/schemas/sequence.schema.json +234 -0
  186. package/skills/archify/schemas/timeline.schema.json +160 -0
  187. package/skills/archify/schemas/tree.schema.json +141 -0
  188. package/skills/archify/schemas/waterfall.schema.json +147 -0
  189. package/skills/archify/schemas/workflow.schema.json +439 -0
  190. package/skills/archify/scripts/check-render-output.mjs +1469 -0
  191. package/skills/archify/scripts/check-update.mjs +1717 -0
  192. package/skills/archify/scripts/delivery-update-child.mjs +26 -0
  193. package/skills/archify/scripts/render-examples.mjs +36 -0
  194. package/skills/archify/scripts/update-contract.mjs +187 -0
  195. package/skills/archify/skill-release.json +10 -0
@@ -0,0 +1,161 @@
1
+ const TARGET_SCHEMA_VERSION = 2;
2
+
3
+ // Planning removes absolute route assertions, but must keep the same rank
4
+ // policy as the document whose coordinates will be mapped into that plan.
5
+ // An authored canvas likewise keeps its established plan: draft-only optional
6
+ // corridor growth must not consume space that a valid fixed canvas lacks.
7
+ const absoluteGeometryPolicy = Symbol('workflow absolute geometry policy');
8
+ export function preservesWorkflowAbsoluteGeometry(workflow) {
9
+ return workflow[absoluteGeometryPolicy] ?? (Array.isArray(workflow.meta?.viewBox) || (workflow.edges || []).some((edge) => (
10
+ Array.isArray(edge.via) || Array.isArray(edge.labelAt)
11
+ || edge.channelX !== undefined || edge.channelY !== undefined
12
+ )));
13
+ }
14
+
15
+ function clone(value) {
16
+ return JSON.parse(JSON.stringify(value));
17
+ }
18
+
19
+ /**
20
+ * Return the authored workflow as a schema-v2 document without its capacity
21
+ * override. The compiler can use this projection to discover the intrinsic v2
22
+ * rank plan before deciding whether an explicit viewBox needs to grow.
23
+ */
24
+ export function intrinsicWorkflow(workflow) {
25
+ const intrinsic = clone(workflow);
26
+ intrinsic.schema_version = TARGET_SCHEMA_VERSION;
27
+ intrinsic[absoluteGeometryPolicy] = preservesWorkflowAbsoluteGeometry(workflow);
28
+ intrinsic.meta = { ...intrinsic.meta };
29
+ delete intrinsic.meta.viewBox;
30
+ return intrinsic;
31
+ }
32
+
33
+ /**
34
+ * Return a schema-v2 planning projection that removes authored route geometry
35
+ * which may only become valid after its legacy X coordinates are remapped.
36
+ * Rank-affecting automatic and straight relationships remain in the projection.
37
+ */
38
+ export function planningWorkflow(workflow) {
39
+ const planned = intrinsicWorkflow(workflow);
40
+ planned.edges = planned.edges.flatMap((edge) => {
41
+ const hasRoutedGeometry = Array.isArray(edge.via)
42
+ || (edge.route && !['auto', 'straight'].includes(edge.route))
43
+ || edge.channelX !== undefined
44
+ || edge.channelY !== undefined;
45
+ if (hasRoutedGeometry) return [];
46
+
47
+ const automatic = {};
48
+ for (const property of ['id', 'from', 'to', 'variant', 'role', 'width']) {
49
+ if (edge[property] !== undefined) automatic[property] = edge[property];
50
+ }
51
+ if (edge.route === 'straight') automatic.route = 'straight';
52
+ if (edge.labelAt === undefined && edge.label !== undefined) automatic.label = edge.label;
53
+ return [automatic];
54
+ });
55
+
56
+ // Full-topology contracts cannot be checked against a layout-only subgraph.
57
+ // The source and final mapped candidate retain and validate these contracts.
58
+ if (planned.edges.length !== workflow.edges.length) delete planned.semanticChecks;
59
+
60
+ if (Array.isArray(planned.mainPath)) {
61
+ const projectedPairs = new Set(planned.edges.map((edge) => `${edge.from}\u0000${edge.to}`));
62
+ const projectionBreaksMainPath = planned.mainPath.some((from, index) => (
63
+ index < planned.mainPath.length - 1
64
+ && !projectedPairs.has(`${from}\u0000${planned.mainPath[index + 1]}`)
65
+ ));
66
+ if (projectionBreaksMainPath) delete planned.mainPath;
67
+ }
68
+
69
+ return planned;
70
+ }
71
+
72
+ function mappedNumber(value) {
73
+ return Number(value.toFixed(6));
74
+ }
75
+
76
+ /**
77
+ * Build a deterministic piecewise-linear mapping between corresponding legacy
78
+ * and readable rank centers. Coordinates outside the rank span are extrapolated
79
+ * using the nearest segment so explicitly authored outside corridors retain
80
+ * their relative offset.
81
+ */
82
+ export function createHorizontalRankMapper(oldColumns, newColumns) {
83
+ if (
84
+ !Array.isArray(oldColumns)
85
+ || !Array.isArray(newColumns)
86
+ || oldColumns.length !== newColumns.length
87
+ || oldColumns.length < 2
88
+ || !oldColumns.every(Number.isFinite)
89
+ || !newColumns.every(Number.isFinite)
90
+ ) {
91
+ throw new TypeError('Horizontal rank mapping requires matching finite column arrays.');
92
+ }
93
+ for (let index = 1; index < oldColumns.length; index += 1) {
94
+ if (oldColumns[index] <= oldColumns[index - 1] || newColumns[index] <= newColumns[index - 1]) {
95
+ throw new TypeError('Horizontal rank mapping requires strictly increasing columns.');
96
+ }
97
+ }
98
+
99
+ return (x) => {
100
+ if (!Number.isFinite(x)) throw new TypeError('Horizontal rank mapping requires a finite x coordinate.');
101
+ let segment = oldColumns.length - 2;
102
+ if (x <= oldColumns[0]) {
103
+ segment = 0;
104
+ } else {
105
+ for (let index = 0; index < oldColumns.length - 1; index += 1) {
106
+ if (x <= oldColumns[index + 1]) {
107
+ segment = index;
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ const oldSpan = oldColumns[segment + 1] - oldColumns[segment];
113
+ const newSpan = newColumns[segment + 1] - newColumns[segment];
114
+ const ratio = (x - oldColumns[segment]) / oldSpan;
115
+ return mappedNumber(newColumns[segment] + ratio * newSpan);
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Apply one horizontal coordinate mapping to every schema-v1 absolute X pin.
121
+ * The caller owns the supplied workflow; this function reports an audit trail
122
+ * for each changed coordinate in stable document order.
123
+ */
124
+ export function mapExplicitCoordinates(workflow, mapX) {
125
+ const changedCoordinates = [];
126
+ const record = (path, owner, property) => {
127
+ const from = owner[property];
128
+ const to = mapX(from);
129
+ owner[property] = to;
130
+ if (to !== from) changedCoordinates.push({ path, from, to });
131
+ };
132
+
133
+ for (const [edgeIndex, edge] of workflow.edges.entries()) {
134
+ if (Array.isArray(edge.via)) {
135
+ for (const [pointIndex, point] of edge.via.entries()) {
136
+ if (Array.isArray(point) && Number.isFinite(point[0])) {
137
+ record(`/edges/${edgeIndex}/via/${pointIndex}/0`, point, 0);
138
+ }
139
+ }
140
+ }
141
+ if (Array.isArray(edge.labelAt) && Number.isFinite(edge.labelAt[0])) {
142
+ record(`/edges/${edgeIndex}/labelAt/0`, edge.labelAt, 0);
143
+ }
144
+ if (Number.isFinite(edge.channelX)) {
145
+ record(`/edges/${edgeIndex}/channelX`, edge, 'channelX');
146
+ }
147
+ }
148
+ return changedCoordinates;
149
+ }
150
+
151
+ /**
152
+ * Construct an independently owned schema-v2 candidate with all authored
153
+ * absolute X pins mapped to the readable rank plan.
154
+ */
155
+ export function createMappedWorkflowCandidate(workflow, oldColumns, newColumns) {
156
+ const document = clone(workflow);
157
+ document.schema_version = TARGET_SCHEMA_VERSION;
158
+ const mapX = createHorizontalRankMapper(oldColumns, newColumns);
159
+ const changedCoordinates = mapExplicitCoordinates(document, mapX);
160
+ return { document, changedCoordinates };
161
+ }
@@ -0,0 +1,94 @@
1
+ // One optional placement proposal for a failed readable workflow.
2
+ // Never applied to the authored input; the compiler verifies the complete result.
3
+ export function proposeWorkflowPlacement(input) {
4
+ if (input.schema_version !== 2 || input.meta?.viewBox
5
+ || input.groups?.length || input.phases?.length
6
+ || !input.mainPath?.length || input.nodes.length > 64
7
+ || input.edges.some(e => ['via', 'channelX', 'channelY', 'labelAt'].some(k => e[k] !== undefined))) {
8
+ return { eligible: false, reason: 'Needs unpinned v2, complete mainPath, no authored regions, <=64 nodes.' };
9
+ }
10
+ const main = input.mainPath;
11
+ const mainIndex = new Map(main.map((id, i) => [id, i]));
12
+ const nodes = new Map(input.nodes.map(n => [n.id, n]));
13
+ const laneIndex = new Map(input.lanes.map((l, i) => [l.id, i]));
14
+ if (main.some(id => !nodes.has(id)) || new Set(main).size !== main.length)
15
+ return { eligible: false, reason: 'Invalid mainPath.' };
16
+ const out = new Map(input.nodes.map(n => [n.id, input.edges.filter(e => e.from === n.id).map(e => e.to)]));
17
+ function reachable(id) {
18
+ const found = new Set(), todo = [...out.get(id)];
19
+ while (todo.length) {
20
+ const next = todo.pop();
21
+ if (found.has(next) || !nodes.has(next)) continue;
22
+ found.add(next); todo.push(...out.get(next));
23
+ }
24
+ return found;
25
+ }
26
+ const reach = new Map(input.nodes.map(n => [n.id, reachable(n.id)]));
27
+ const offMain = input.nodes.filter(n => !mainIndex.has(n.id));
28
+ const offIds = new Set(offMain.map(n => n.id));
29
+ const components = [], assigned = new Set();
30
+ for (const n of offMain) {
31
+ if (assigned.has(n.id)) continue;
32
+ const component = [], todo = [n.id];
33
+ while (todo.length) {
34
+ const id = todo.pop();
35
+ if (assigned.has(id)) continue;
36
+ assigned.add(id); component.push(id);
37
+ for (const e of input.edges) {
38
+ if (e.from === id && offIds.has(e.to)) todo.push(e.to);
39
+ if (e.to === id && offIds.has(e.from)) todo.push(e.from);
40
+ }
41
+ }
42
+ const cyclic = component.some(id => main.some(m => reach.get(id).has(m) && reach.get(m).has(id)));
43
+ components.push({ ids: component, cyclic });
44
+ }
45
+ const leftRail = components.some(c => c.cyclic);
46
+ if (!leftRail) return { eligible: false, reason: 'No return cycle connected to the main path.' };
47
+ const rightRail = components.some(c => !c.cyclic && c.ids.length > 1);
48
+ const start = leftRail ? 1 : 0, end = rightRail ? 4 : 5;
49
+ const proposed = structuredClone(input), moved = new Map(proposed.nodes.map(n => [n.id, n]));
50
+ let col = start;
51
+ for (let i = 0; i < main.length; i++) {
52
+ const current = moved.get(main[i]), previous = moved.get(main[i - 1]);
53
+ if (previous && laneIndex.get(current.lane) <= laneIndex.get(previous.lane)) col = Math.min(col + 1, end);
54
+ current.col = col;
55
+ }
56
+ for (const component of components) {
57
+ for (const id of component.ids) {
58
+ const n = moved.get(id);
59
+ const parents = input.edges.filter(e => e.to === id && mainIndex.has(e.from)).map(e => moved.get(e.from));
60
+ n.col = component.cyclic ? 0 : component.ids.length > 1 ? 5
61
+ : Math.min(end, Math.max(start, ...parents.map(p => p.col + 1)));
62
+ // Place same-lane failure branches below their main-path source row.
63
+ if (component.cyclic && parents.some(p => p.lane === n.lane)) {
64
+ const sameLane = main.map(m => moved.get(m)).filter(m => m.lane === n.lane);
65
+ n.yOffset = Math.max(...sameLane.map(m => (m.yOffset || 0) + (m.height || 52) / 2)) + (n.height || 52) / 2 + 40;
66
+ }
67
+ }
68
+ }
69
+ const groups = new Map();
70
+ for (const n of proposed.nodes) {
71
+ const key = n.lane + ':' + n.col;
72
+ groups.set(key, [...groups.get(key) || [], n]);
73
+ }
74
+ function order(n) {
75
+ if (mainIndex.has(n.id)) return mainIndex.get(n.id);
76
+ const incoming = input.edges.filter(e => e.to === n.id && mainIndex.has(e.from)).map(e => mainIndex.get(e.from));
77
+ return incoming.length ? Math.min(...incoming) + 0.5 : main.length;
78
+ }
79
+ for (const group of groups.values()) {
80
+ if (group.length < 2) continue;
81
+ group.sort((a, b) => order(a) - order(b) || a.id.localeCompare(b.id));
82
+ const total = group.reduce((sum, n) => sum + (n.height || 52), 0) + 40 * (group.length - 1);
83
+ let top = -total / 2;
84
+ for (const n of group) {
85
+ n.yOffset = top + (n.height || 52) / 2;
86
+ top += (n.height || 52) + 40;
87
+ }
88
+ }
89
+ const changes = [];
90
+ for (let i = 0; i < input.nodes.length; i++) for (const field of ['col', 'yOffset']) {
91
+ if (input.nodes[i][field] !== proposed.nodes[i][field]) changes.push({op: field in input.nodes[i] ? 'replace' : 'add', path: `/nodes/${i}/${field}`, value: proposed.nodes[i][field]});
92
+ }
93
+ return {eligible: true, proposed, changes, components, rails: {left: leftRail, right: rightRail}};
94
+ }
@@ -0,0 +1,258 @@
1
+ # Archify JSON IR Schemas
2
+
3
+ Each typed renderer consumes a JSON intermediate representation (IR) validated
4
+ against one of the schemas in this folder before any layout work happens.
5
+
6
+ ## Files
7
+
8
+ | Schema | Governs | Structural arrays |
9
+ |--------|---------|-------------------|
10
+ | `workflow.schema.json` | `diagram_type: "workflow"` | `lanes`, `phases`, `groups`, `mainPath`, `nodes`, `edges` |
11
+ | `sequence.schema.json` | `diagram_type: "sequence"` | `participants`, `segments`, `messages`, `activations` |
12
+ | `dataflow.schema.json` | `diagram_type: "dataflow"` | `stages`, `nodes`, `flows` |
13
+ | `lifecycle.schema.json` | `diagram_type: "lifecycle"` | `mainPath`, `states`, `transitions` |
14
+ | `architecture.schema.json` | `diagram_type: "architecture"` | `components`, `boundaries`, `connections` |
15
+ | `erd.schema.json` | `diagram_type: "erd"` | `entities`, `relationships` |
16
+ | `tree.schema.json` | `diagram_type: "tree"` | `nodes` (each with one `parent`) |
17
+ | `class.schema.json` | `diagram_type: "class"` | `types`, `relationships` |
18
+ | `timeline.schema.json` | `diagram_type: "timeline"` | `events` (`lanes` optional) |
19
+ | `waterfall.schema.json` | `diagram_type: "waterfall"` | `spans` (each with optional `parent`) |
20
+ | `common.schema.json` | shared `$defs` only (no top-level document) | — |
21
+
22
+ Every diagram schema requires `schema_version`, `diagram_type`, `meta` (with
23
+ `title` and a durable portable `output`), and its structural arrays — except `segments`, `activations`, ERD and class
24
+ `relationships`, and `cards`, which are optional — and sets `additionalProperties: false` at every
25
+ level, so unknown fields are rejected rather than silently ignored.
26
+
27
+ Every `meta` object also accepts `animation: "trace"` for opt-in SVG/CSS motion
28
+ in generated HTML. Omit it, or set `"none"`, for the default static output.
29
+ It also accepts `locale`, any well-formed language tag (schema pattern
30
+ `^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$`). The field selects the fixed Viewer UI,
31
+ renderer-owned default legend and accessibility copy, document-title suffix,
32
+ and `<html lang>` value; it does not translate authored strings. The bundled
33
+ catalogs enrolled in `locales/manifest.json` (`en`, `zh-CN`, `zh-TW`, `es`, `ko`, `ar`) are
34
+ selected by tag, case-insensitively; region and script variants are distinct
35
+ tags. Any other tag needs a matching `translations` object (see below) or the
36
+ renderer falls back to English and discloses it.
37
+ Omitting `locale` preserves legacy behavior and resolves to English.
38
+ Malformed locale tags fail schema validation instead of being guessed or
39
+ silently rewritten.
40
+
41
+ `meta.translations` is an optional per-key override, as data: an object mapping
42
+ canonical message keys (`locales/en.json`) to translated strings whose
43
+ `{placeholder}` tokens match the English source. Each message resolves as:
44
+ valid `translations` value → bundled catalog for `locale` → English. An
45
+ unrecognized key or mismatched placeholders never replaces a valid
46
+ lower-priority message and fails no render; `validate`/`render`/`deliver`
47
+ report rejected entries and the final resolved coverage to stderr.
48
+ `visual_preset` accepts `classic` (the stable default), `signal-flow` (luminous
49
+ motion-forward presentation), `blueprint` (high-contrast engineering review),
50
+ or `editorial` (warm publication-style design review and documentation).
51
+ Presets change only viewer styling; they do not alter semantic IDs or geometry.
52
+ Sequence `meta` additionally accepts `column_fit`. The default `spread` applies whether or not `meta.viewBox` is supplied.
53
+ Explicit `fixed` keeps the historical 108px column gap and 86px participant
54
+ boxes. `spread` derives column distance and box width from the viewBox, with
55
+ at least a 16px gutter between participant cards, which turns a
56
+ wide canvas into column distance and label room rather than empty space on the
57
+ right. Lane order, IDs, and message semantics are unchanged either way.
58
+
59
+ `meta.views` is retired. The schemas still accept the old guided-view shape so
60
+ existing files keep validating, but renderers ignore it; do not author it.
61
+
62
+ ### Legend presentation contract
63
+
64
+ Every `meta` object accepts the same optional legend shape without changing
65
+ the schema version already selected for that renderer:
66
+
67
+ ```json
68
+ "legend": {
69
+ "mode": "auto",
70
+ "entries": {
71
+ "security": { "label": "restricted data", "visible": true }
72
+ }
73
+ }
74
+ ```
75
+
76
+ `mode` is `auto` (the default), `all`, or `hidden`. `auto` includes only kinds
77
+ present in typed IR; `all` includes the renderer's full stable catalog;
78
+ `hidden` removes the complete legend and takes precedence over entry overrides.
79
+ Architecture documents that omit an explicit `viewBox` size that automatic
80
+ viewBox from the same measured resolved legend footprint used for final SVG
81
+ layout. Across all renderers, legacy documents that omit `meta.legend` use a
82
+ compatibility-safe implicit `auto`: if the resolved legend cannot fit an
83
+ explicit authored viewBox without overlap, Archify omits the complete legend
84
+ instead of turning a previously valid schema-v1 document into a hard failure.
85
+ Once an author adds `meta.legend` (including explicit `mode: "auto"`), the
86
+ layout is intentional and unfit labels or bands fail with a path-prefixed
87
+ diagnostic. An entry may set a non-empty, bounded `label`, boolean `visible`,
88
+ or both.
89
+ `visible: false` removes a resolved entry and `visible: true` forces a supported
90
+ but unused kind into the visual legend. Unknown kinds and properties fail
91
+ strict validation.
92
+
93
+ Supported keys are renderer-owned:
94
+
95
+ | Renderer | `meta.legend.entries` keys |
96
+ |---|---|
97
+ | Architecture | `frontend`, `backend`, `database`, `cloud`, `security`, `messagebus`, `external` |
98
+ | Workflow | `frontend`, `backend`, `security`, `messagebus`, `database`, `cloud`, `external` |
99
+ | Sequence | `emphasis`, `return`, `security`, `dashed`, `default` |
100
+ | Dataflow | `emphasis`, `security`, `dashed`, `database`, `default` |
101
+ | Lifecycle | `start`, `active`, `waiting`, `decision`, `success`, `failure`, `neutral`, `external` |
102
+ | ERD | `pk`, `fk`, `uk`, `one`, `many`, `optional` |
103
+ | Class | `dependency`, `association`, `inheritance`, `realization`, `composition`, `aggregation` |
104
+
105
+ Labels are presentation only: they do not rename the stable kind, change
106
+ nodes/relationships, or create Semantic Lens edge facts. Sequence message and
107
+ Dataflow flow-variant entries are visual keys. Component/state entries backed
108
+ by exact compiled node facts receive the interactive Semantic Legend bridge;
109
+ this includes Dataflow `database` when a real `nodes[].type: "database"` fact
110
+ exists.
111
+
112
+ Every relationship collection (`connections`, `edges`, `messages`, `flows`, and
113
+ `transitions`) accepts an optional author-controlled `id` using the shared ID
114
+ pattern. The renderer keeps its source-order runtime key separately, while the
115
+ authored ID enables a stable `#relation=<id>` viewer link that survives array
116
+ reordering. ID-less documents remain valid and their relationship pins stay
117
+ local to the current page.
118
+
119
+ Every semantic node collection (`components`, `nodes`, `participants`, and
120
+ `states`) also accepts one optional `brand`: either a canonical string returned
121
+ by `archify brands --json`, or a digest-pinned `{ "url", "sha256" }` object
122
+ returned by `archify brands capture <url> --json`. Known IDs and known-brand
123
+ domains use the bundled vector catalogue. Unknown URLs must be captured in that
124
+ explicit command before authoring; render and validate never perform an
125
+ unpinned network capture. Unsafe, unavailable, changed, or unsupported content
126
+ fails closed with a brand diagnostic. Omitted `brand` preserves the prior
127
+ output.
128
+
129
+ ## schema_version policy
130
+
131
+ Workflow supports schema versions 1 and 2. Version 1 remains the fixed-layout
132
+ compatibility contract; version 2 opts into the readable workflow compiler and
133
+ can be produced explicitly with `archify migrate workflow ... --to-schema 2`.
134
+ Lifecycle accepts only version 3, an automatic main-path layout (see the
135
+ [lifecycle renderer](../renderers/lifecycle/README.md)).
136
+ The other three diagram schemas keep `schema_version` pinned to `1`; they have
137
+ no schema-version migration command. For any of the five diagram types, repair
138
+ a legacy missing or nonportable `meta.output` in the source and run `validate`.
139
+ For a workflow v1-to-v2 migration specifically, `migrate workflow` also accepts
140
+ `--output reports/diagram.html` to put that portable value in the separate v2
141
+ destination without changing the legacy source.
142
+
143
+ Workflow also accepts optional `semanticChecks`. `allowedRoots` and
144
+ `allowedTerminals` close the set of intentional graph sources and sinks;
145
+ `requiredEdges` requires exact authored relationships; and `requiredPaths`
146
+ requires directed reachability while allowing intermediate nodes. The compiler
147
+ evaluates these facts before layout and returns typed `workflow/*` diagnostics.
148
+ The field is additive and geometry-neutral: omitting it preserves existing
149
+ workflow behavior and including a satisfied contract does not change SVG or
150
+ layout-receipt bytes.
151
+
152
+ A file that validates today must keep validating and rendering within its
153
+ declared version throughout the 2.x release line. The explicitly reviewed
154
+ portable-output hardening is the one exception: older v1 documents that omit
155
+ `meta.output` must add a portable POSIX-relative `.html` path (for example,
156
+ `reports/diagram.html`); ordinary explicit CLI output arguments do not replace
157
+ this durable authored value. The workflow-only v1-to-v2 migration command may
158
+ instead receive that value explicitly as `--output reports/diagram.html`; it
159
+ writes the value only to its separate validated destination. Additive viewer,
160
+ accessibility, and presentation improvements may enhance generated HTML, but
161
+ they must not reinterpret authored IR or turn a previously valid profile-less
162
+ v1 file into a new hard layout failure. Breaking IR changes require a new
163
+ version; additive, backwards-compatible fields do not.
164
+
165
+ ## Shared definitions (common.schema.json)
166
+
167
+ The five diagram schemas reference `common.schema.json#/$defs/...`:
168
+
169
+ - `id` — element identifiers, pattern `^[a-zA-Z][a-zA-Z0-9_-]*$`
170
+ - `point` — an `[x, y]` pair of numbers (used by `via` and `labelAt`)
171
+ - `componentType` — `frontend`, `backend`, `database`, `cloud`, `security`,
172
+ `messagebus`, `external`
173
+ - `locale` — a well-formed renderer locale tag (bundled catalogs are listed in
174
+ `locales/manifest.json`; any other tag needs a matching `translations` object)
175
+ - `translations` — canonical message key → translated string, layered over the
176
+ bundled catalog for `locale`, then English
177
+ - `portableOutputPath` — the portable POSIX-relative `.html` path used by
178
+ `meta.output`; see the two output-path boundaries in the
179
+ [delivery contract](../references/delivery-contract.md#output-path-contracts)
180
+ - `brandMark` — one optional built-in brand ID or explicit HTTP(S) site URL
181
+ - `variant` — `default`, `emphasis`, `security`, `dashed` (sequence messages
182
+ extend this list locally with `return`)
183
+ - `legendMode` and `legendEntry` — the shared strict mode and label/visibility
184
+ override shapes used by each renderer-owned key map
185
+ - `guidedViews` — the retired `meta.views` shape, still accepted and ignored
186
+ - `cards` — the summary-card blocks rendered below the SVG
187
+
188
+ Lifecycle state `type` is mode-specific (`start`/`active`/`waiting`/...) and
189
+ stays in `lifecycle.schema.json`.
190
+
191
+ The JSON Schema definition is the portable contract's structurally expressible
192
+ preflight. The shipped generated validator wrapper and CLI additionally enforce
193
+ byte-based component limits and the complete runtime path contract; consumers
194
+ that need the same cross-platform acceptance boundary should use that wrapper.
195
+
196
+ ## Runtime validation
197
+
198
+ At development time, `scripts/generate-validators.mjs` compiles all five
199
+ schemas with ajv's draft 2020-12 standalone generator using `strict: true` and
200
+ `allErrors: true`. The generated `renderers/shared/generated-validators.mjs`
201
+ is committed and shipped with the skill, so runtime validation has no npm or
202
+ network dependency. `renderers/shared/validator.mjs` applies the matching
203
+ standalone validator before the renderer's own layout checks.
204
+ The shared loader then checks cross-collection facts that JSON Schema cannot
205
+ express cleanly here: duplicate view IDs, duplicate focus IDs, focus IDs that do
206
+ not exist in the diagram's semantic collection, and duplicate authored
207
+ relationship IDs within the mode's relationship collection.
208
+
209
+ All five modes support opt-in, revision-pinned repository evidence.
210
+ `meta.repository` names the repository URL and full commit SHA, with optional
211
+ `provider` (`github`, `gitee`, or `gitlab`) and `link_mode` (`web` or `local-only`; see the
212
+ authoring contract); a node may carry one to three `sources` with repo-relative POSIX paths, optional line
213
+ ranges, and optional labels. Sources are authored on the mode's own node
214
+ collection — Architecture `components`, Workflow and Data Flow `nodes`,
215
+ Sequence `participants`, Lifecycle `states` — and the verified payload is keyed
216
+ by node id. Shape is schema-checked, then the renderer requires
217
+ `--repo-root`: the local Git origin must match, and Git must prove the commit,
218
+ blobs, and requested lines. Verified evidence is embedded outside the canonical
219
+ SVG for the Semantic Passport and Node Finder; ordinary documents and visual
220
+ exports carry no repository evidence.
221
+
222
+ ## Visual quality and engineering truth
223
+
224
+ `meta.quality_profile` and `meta.engineering_profile` answer different
225
+ questions. `quality_profile` is available in all five modes and controls how
226
+ strictly Archify judges composition. `engineering_profile` is an optional
227
+ Architecture-only semantic contract; omitting it preserves the ordinary v1
228
+ behavior.
229
+
230
+ The first engineering profile is `deployment-ownership`. Enable it only when
231
+ the user wants a fail-closed deployment review and the source facts are known.
232
+ It requires every non-external component to name an owner in `tag` and belong
233
+ to exactly one `region`; the document must contain both `region` and
234
+ `security-group` boundaries; every `database` must be inside a
235
+ `security-group`; each security group must contain members from one shared
236
+ region; and every connection whose region or security-group membership changes
237
+ must name the real crossing mechanism in `label`.
238
+
239
+ The profile validates only authored IR. It does not discover infrastructure,
240
+ infer owners, or prove that a diagram matches a live environment. If a fact is
241
+ unknown, leave the profile unset or obtain the fact instead of inventing it.
242
+
243
+ `npm test` runs the generator in check mode and fails when the committed
244
+ validators drift from their schemas.
245
+
246
+ ## Error format
247
+
248
+ Schema violations exit non-zero. Each ajv error is reported on its own line as
249
+ the instance path — annotated with the nearest enclosing element's `id` or
250
+ `label` — followed by the message and parameters:
251
+
252
+ ```text
253
+ workflow schema validation failed:
254
+ /nodes/3 (id/label: "router") must NOT have additional properties {"additionalProperty":"colour"}
255
+ ```
256
+
257
+ Schemas catch shape errors (types, enums, ranges, unknown fields); geometry
258
+ problems such as overlaps and label collisions are the renderers' job.
@@ -0,0 +1,152 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/tt-a1i/archify/schemas/architecture.schema.json",
4
+ "title": "Archify Architecture Diagram",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "diagram_type", "meta", "components"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "diagram_type": { "const": "architecture" },
11
+ "meta": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "required": ["title", "output"],
15
+ "properties": {
16
+ "title": { "type": "string", "minLength": 1 },
17
+ "locale": { "$ref": "common.schema.json#/$defs/locale" },
18
+ "translations": { "$ref": "common.schema.json#/$defs/translations" },
19
+ "subtitle": { "type": "string" },
20
+ "output": { "$ref": "common.schema.json#/$defs/portableOutputPath" },
21
+ "animation": { "$ref": "common.schema.json#/$defs/animation" },
22
+ "visual_preset": { "$ref": "common.schema.json#/$defs/visualPreset" },
23
+ "quality_profile": { "$ref": "common.schema.json#/$defs/qualityProfile" },
24
+ "engineering_profile": { "enum": ["deployment-ownership"] },
25
+ "repository": { "$ref": "common.schema.json#/$defs/repository" },
26
+ "views": { "$ref": "common.schema.json#/$defs/guidedViews" },
27
+ "legend": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "mode": { "$ref": "common.schema.json#/$defs/legendMode" },
32
+ "entries": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "properties": {
36
+ "frontend": { "$ref": "common.schema.json#/$defs/legendEntry" },
37
+ "backend": { "$ref": "common.schema.json#/$defs/legendEntry" },
38
+ "database": { "$ref": "common.schema.json#/$defs/legendEntry" },
39
+ "cloud": { "$ref": "common.schema.json#/$defs/legendEntry" },
40
+ "security": { "$ref": "common.schema.json#/$defs/legendEntry" },
41
+ "messagebus": { "$ref": "common.schema.json#/$defs/legendEntry" },
42
+ "external": { "$ref": "common.schema.json#/$defs/legendEntry" }
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "viewBox": {
48
+ "type": "array",
49
+ "prefixItems": [
50
+ { "type": "number", "minimum": 320 },
51
+ { "type": "number", "minimum": 240 }
52
+ ],
53
+ "items": false,
54
+ "minItems": 2,
55
+ "maxItems": 2
56
+ }
57
+ }
58
+ },
59
+ "layout": {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "required": ["mode"],
63
+ "properties": {
64
+ "mode": { "enum": ["grid"] },
65
+ "origin": { "$ref": "common.schema.json#/$defs/point" },
66
+ "cols": { "type": "integer", "minimum": 1, "maximum": 12 },
67
+ "gapX": { "type": "number", "minimum": 0 },
68
+ "gapY": { "type": "number", "minimum": 0 },
69
+ "cellW": { "type": "number", "minimum": 40 },
70
+ "cellH": { "type": "number", "minimum": 24 }
71
+ }
72
+ },
73
+ "components": {
74
+ "type": "array",
75
+ "minItems": 1,
76
+ "items": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "required": ["id", "type", "label"],
80
+ "properties": {
81
+ "id": { "$ref": "common.schema.json#/$defs/id" },
82
+ "type": { "$ref": "common.schema.json#/$defs/componentType" },
83
+ "label": { "type": "string", "minLength": 1 },
84
+ "sublabel": { "type": "string" },
85
+ "tag": { "type": "string" },
86
+ "icon": { "$ref": "common.schema.json#/$defs/nodeIcon" },
87
+ "brand": { "$ref": "common.schema.json#/$defs/brandMark" },
88
+ "sources": { "$ref": "common.schema.json#/$defs/sourceReferences" },
89
+ "row": { "type": "integer", "minimum": 0 },
90
+ "col": { "type": "integer", "minimum": 0 },
91
+ "pos": { "$ref": "common.schema.json#/$defs/point" },
92
+ "size": {
93
+ "type": "array",
94
+ "prefixItems": [
95
+ { "type": "number", "exclusiveMinimum": 0 },
96
+ { "type": "number", "exclusiveMinimum": 0 }
97
+ ],
98
+ "items": false,
99
+ "minItems": 2,
100
+ "maxItems": 2
101
+ }
102
+ }
103
+ }
104
+ },
105
+ "boundaries": {
106
+ "type": "array",
107
+ "items": {
108
+ "type": "object",
109
+ "additionalProperties": false,
110
+ "required": ["kind", "label", "wraps"],
111
+ "properties": {
112
+ "kind": { "enum": ["region", "security-group"] },
113
+ "label": { "type": "string", "minLength": 1 },
114
+ "wraps": {
115
+ "type": "array",
116
+ "minItems": 1,
117
+ "items": { "$ref": "common.schema.json#/$defs/id" }
118
+ },
119
+ "pad": { "type": "number", "minimum": 0 }
120
+ }
121
+ }
122
+ },
123
+ "connections": {
124
+ "type": "array",
125
+ "items": {
126
+ "type": "object",
127
+ "additionalProperties": false,
128
+ "required": ["from", "to"],
129
+ "properties": {
130
+ "id": { "$ref": "common.schema.json#/$defs/id" },
131
+ "from": { "$ref": "common.schema.json#/$defs/id" },
132
+ "to": { "$ref": "common.schema.json#/$defs/id" },
133
+ "label": { "type": "string" },
134
+ "variant": { "$ref": "common.schema.json#/$defs/variant" },
135
+ "fromSide": { "$ref": "common.schema.json#/$defs/side" },
136
+ "toSide": { "$ref": "common.schema.json#/$defs/side" },
137
+ "route": { "enum": ["auto", "straight", "orthogonal-h", "orthogonal-v"] },
138
+ "via": {
139
+ "type": "array",
140
+ "items": { "$ref": "common.schema.json#/$defs/point" }
141
+ },
142
+ "labelAt": { "$ref": "common.schema.json#/$defs/point" },
143
+ "labelDx": { "type": "number" },
144
+ "labelDy": { "type": "number" },
145
+ "labelSegment": { "type": "integer", "minimum": 0 },
146
+ "width": { "$ref": "common.schema.json#/$defs/relationshipWidth" }
147
+ }
148
+ }
149
+ },
150
+ "cards": { "$ref": "common.schema.json#/$defs/cards" }
151
+ }
152
+ }