@exellix/graphs-studio-data-flow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts ADDED
@@ -0,0 +1,246 @@
1
+ import type { GraphCatalogs, GraphInspection } from '@exellix/graph-engine/dist/src/inspection/types.js';
2
+ import type { GraphContractsInspection, NodeContractInspection } from '@exellix/graph-engine/dist/src/inspection/contractTypes.js';
3
+
4
+ export type EdgeFlowType = 'structural' | 'data' | 'control' | 'mixed';
5
+
6
+ export type EdgeAnalysis = {
7
+ edgeKey: string;
8
+ edgeIndex: number;
9
+ from: string;
10
+ to: string;
11
+ when?: unknown;
12
+ flowType: EdgeFlowType;
13
+ writtenPaths: string[];
14
+ readPaths: string[];
15
+ matchedWritesToReads: string[];
16
+ predicatePaths: string[];
17
+ predicatePathTails: string[];
18
+ predicateProviders: string[];
19
+ predicateUnresolved: boolean;
20
+ branchType:
21
+ | 'unconditional'
22
+ | 'conditional-any'
23
+ | 'conditional-all'
24
+ | 'conditional-path'
25
+ | 'conditional';
26
+ hasStructuralRole: boolean;
27
+ };
28
+
29
+ export type GraphIssueSeverity = 'error' | 'warning' | 'info';
30
+
31
+ export type GraphIssueCategory =
32
+ | 'structural'
33
+ | 'contract'
34
+ | 'routing'
35
+ | 'readability'
36
+ | 'validation'
37
+ | 'scoping'
38
+ | 'narrix'
39
+ | 'finalizer'
40
+ | 'graph-hygiene';
41
+
42
+ export type GraphIssueScope = 'graph' | 'node' | 'edge';
43
+
44
+ export type GraphIssue = {
45
+ id: string;
46
+ severity: GraphIssueSeverity;
47
+ scopeType: GraphIssueScope;
48
+ scopeId: string;
49
+ category: GraphIssueCategory;
50
+ title: string;
51
+ description: string;
52
+ evidence?: Record<string, unknown>;
53
+ recommendedAction?: string;
54
+ };
55
+
56
+ export type GraphSummary = {
57
+ nodeCount: number;
58
+ edgeCount: number;
59
+ entryNodeCount: number;
60
+ finalizerCount: number;
61
+ aiTaskCount: number;
62
+ localSkillCount: number;
63
+ conditionalEdgeCount: number;
64
+ routingNodeCount: number;
65
+ issueCounts: { error: number; warning: number; info: number };
66
+ readabilityCoverage: { withTitle: number; withAsks: number; withKind: number };
67
+ };
68
+
69
+ export type AnalysisStatus = 'ok' | 'error';
70
+
71
+ // --- I/O Resolution types ---
72
+
73
+ export type IONodeLayer = 'scope' | 'inference' | 'write-back' | 'finalizer';
74
+
75
+ export type IOEntryTier = 'graph' | 'runtime';
76
+
77
+ export type IOEntry = {
78
+ /** Dot-separated execution memory path, or "xmemory-op:{id}", or "finalOutput" */
79
+ path: string;
80
+ tier: IOEntryTier;
81
+ /** Human-readable source label (e.g. "outputMapping", "narrix preprocessor") */
82
+ source: string;
83
+ /** For writes: target store ("execution", "xmemory-op", etc.) */
84
+ target?: string;
85
+ /** Extra context (collection name, mode, fallback label, etc.) */
86
+ detail?: string;
87
+ /** When true, only show in the inspector, not on the node card */
88
+ _inspectorOnly?: boolean;
89
+ /** Marks the primary narrix read entry */
90
+ _narrixPrimary?: boolean;
91
+ };
92
+
93
+ export type ResolvedNodeIO = {
94
+ layer: IONodeLayer;
95
+ reads: IOEntry[];
96
+ writes: IOEntry[];
97
+ };
98
+
99
+ export type IOLink = {
100
+ writtenBy: string;
101
+ writePath: string;
102
+ readBy: string;
103
+ readPath: string;
104
+ };
105
+
106
+ export type DiagramEdgeKind = 'topology' | 'data' | 'control' | 'mixed';
107
+
108
+ export type DiagramEdge = {
109
+ key: string;
110
+ index: number;
111
+ from: string;
112
+ to: string;
113
+ kind: DiagramEdgeKind;
114
+ hasCondition: boolean;
115
+ dataFlowLinks: IOLink[];
116
+ dataFlowPaths: string[];
117
+ readPaths: string[];
118
+ tooltip: string;
119
+ };
120
+
121
+ export type DiagramModel = {
122
+ topologyEdges: DiagramEdge[];
123
+ dataFlowLinks: IOLink[];
124
+ virtualBoundaryEdges: Array<{ from: string; to: string }>;
125
+ unresolvedReads: DanglingRead[];
126
+ orphanedWrites: OrphanedWrite[];
127
+ edgesByIndex: DiagramEdge[];
128
+ edgesByKey: Record<string, DiagramEdge>;
129
+ };
130
+
131
+ export type RuntimeFinalizerReadStatus = 'connected' | 'no-authored-edge' | 'missing-writer';
132
+
133
+ export type RuntimeFinalizerReadContract = {
134
+ key: string;
135
+ finalizerId: string;
136
+ finalizerTitle: string;
137
+ readPath: string;
138
+ readSource: string;
139
+ authoredSource: string;
140
+ writerNodeId: string;
141
+ writerTitle: string;
142
+ writerPath: string;
143
+ matchingWriterCount: number;
144
+ matchingAuthoredEdgeIndex: number;
145
+ matchingAuthoredEdgeKey: string;
146
+ status: RuntimeFinalizerReadStatus;
147
+ statusLabel: string;
148
+ };
149
+
150
+ export type RuntimeFinalizerContract = {
151
+ finalizerId: string;
152
+ finalizerTitle: string;
153
+ finalizerType: string;
154
+ strategy: string;
155
+ reads: RuntimeFinalizerReadContract[];
156
+ connectedCount: number;
157
+ missingCount: number;
158
+ totalCount: number;
159
+ statusLabel: string;
160
+ };
161
+
162
+ export type RuntimeEdgeContract = {
163
+ key: string;
164
+ index: number;
165
+ from: string;
166
+ to: string;
167
+ kind: DiagramEdgeKind;
168
+ runtimeDataPaths: string[];
169
+ dataFlowLinks: IOLink[];
170
+ sourceWritePaths: string[];
171
+ targetReadPaths: string[];
172
+ explanation: string;
173
+ fixHint: string;
174
+ };
175
+
176
+ export type RuntimeDataContract = {
177
+ finalizerContracts: RuntimeFinalizerContract[];
178
+ edgeContractsByIndex: RuntimeEdgeContract[];
179
+ edgeContractsByKey: Record<string, RuntimeEdgeContract>;
180
+ summary: {
181
+ dataHandoffEdgeCount: number;
182
+ topologyOnlyEdgeCount: number;
183
+ conditionalEdgeCount: number;
184
+ finalizerReadCount: number;
185
+ connectedFinalizerReadCount: number;
186
+ missingFinalizerReadCount: number;
187
+ finalizerInputStatusLabel: string;
188
+ };
189
+ };
190
+
191
+ export type DanglingRead = {
192
+ nodeId: string;
193
+ path: string;
194
+ tier: IOEntryTier;
195
+ source: string;
196
+ };
197
+
198
+ export type OrphanedWrite = {
199
+ nodeId: string;
200
+ path: string;
201
+ tier: IOEntryTier;
202
+ source: string;
203
+ };
204
+
205
+ // --- Simplified filter state ---
206
+
207
+ export type AnalysisFilters = {
208
+ layer: 'all' | IONodeLayer;
209
+ hasIssues: boolean;
210
+ };
211
+
212
+ export const defaultAnalysisFilters: AnalysisFilters = {
213
+ layer: 'all',
214
+ hasIssues: false,
215
+ };
216
+
217
+ // --- Graph analysis result ---
218
+
219
+ export type GraphAnalysisResult = {
220
+ status: AnalysisStatus;
221
+ errorMessage?: string;
222
+ graphInspection: GraphInspection;
223
+ graphContractInspection: GraphContractsInspection;
224
+ graphSummary: GraphSummary;
225
+ /** Same as graphContractInspection.nodes — alias for inspector */
226
+ nodeAnalysisById: Record<string, NodeContractInspection>;
227
+ edgeAnalysisByKey: Record<string, EdgeAnalysis>;
228
+ edgeAnalysisByIndex: EdgeAnalysis[];
229
+ graphIssues: GraphIssue[];
230
+ issuesByNodeId: Record<string, GraphIssue[]>;
231
+ issuesByEdgeKey: Record<string, GraphIssue[]>;
232
+ executionOrder: string[];
233
+ catalogs: GraphCatalogs;
234
+ /** Resolved I/O for each node (Tier 1 + Tier 2) */
235
+ nodeIO: Record<string, ResolvedNodeIO>;
236
+ /** Cross-node write-to-read links */
237
+ ioLinks: IOLink[];
238
+ /** Reads with no matching upstream write */
239
+ danglingReads: DanglingRead[];
240
+ /** Writes no downstream node reads */
241
+ orphanedWrites: OrphanedWrite[];
242
+ /** Runtime-backed diagram semantics consumed by the graph canvas. */
243
+ diagramModel: DiagramModel;
244
+ /** Studio-facing runtime data summary derived from graph-engine inspection. */
245
+ runtimeDataContract: RuntimeDataContract;
246
+ };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Design-level web scoping intent (`planning.webScoping`), separate from runtime
3
+ * `taskConfiguration.aiTaskProfile.webQueryTemplate` (Graphenix 2.7.3 PRE webScope unit).
4
+ *
5
+ * Persisted values: `always` | `ai-decision` | `never`.
6
+ * Legacy values are normalized on read: `required`→`always`, `optional`→`ai-decision`, `disabled`→`never`.
7
+ *
8
+ * @typedef {'always'|'ai-decision'|'never'} WebScopingPlanningMode
9
+ * @typedef {'declared'|'aiTaskProfile-fallback'|'default'} WebScopingPlanningSource
10
+ */
11
+ import { readNodeWebScopeEnabled } from './lib/nodeMetadataAccessors.js';
12
+
13
+ export const WEB_SCOPING_PLANNING_MODES = /** @type {const} */ ({
14
+ always: 'always',
15
+ aiDecision: 'ai-decision',
16
+ never: 'never',
17
+ });
18
+
19
+ const VALID = new Set(Object.values(WEB_SCOPING_PLANNING_MODES));
20
+
21
+ /** @type {Record<string, WebScopingPlanningMode>} */
22
+ const LEGACY_TO_CANONICAL = {
23
+ required: 'always',
24
+ optional: 'ai-decision',
25
+ disabled: 'never',
26
+ };
27
+
28
+ /**
29
+ * @param {unknown} raw
30
+ * @returns {WebScopingPlanningMode|undefined}
31
+ */
32
+ function canonicalizeStoredWebScopingPlanning(raw) {
33
+ if (typeof raw !== 'string') return undefined;
34
+ const mapped = LEGACY_TO_CANONICAL[raw] ?? raw;
35
+ if (!VALID.has(mapped)) return undefined;
36
+ return /** @type {WebScopingPlanningMode} */ (mapped);
37
+ }
38
+
39
+ /** Skill keys that may declare planning web scoping in the editor. */
40
+ const WEB_SCOPING_PLANNING_SKILL_KEYS = new Set(['professional-answer']);
41
+
42
+ /**
43
+ * @param {object|null|undefined} node
44
+ * @returns {boolean}
45
+ */
46
+ export function supportsWebScopingPlanning(node) {
47
+ if (!node || typeof node !== 'object' || node.isVirtual) return false;
48
+ if (node.type === 'finalizer') return false;
49
+ const sk = node.skillKey;
50
+ if (typeof sk !== 'string' || !sk) return false;
51
+ return WEB_SCOPING_PLANNING_SKILL_KEYS.has(sk);
52
+ }
53
+
54
+ /**
55
+ * Declared mode from authored JSON (legacy strings normalized to canonical).
56
+ * @param {object|null|undefined} node
57
+ * @returns {WebScopingPlanningMode|undefined}
58
+ */
59
+ export function getDeclaredWebScopingPlanning(node) {
60
+ const fromRoot =
61
+ node &&
62
+ typeof node === 'object' &&
63
+ !Array.isArray(node) &&
64
+ node.planning &&
65
+ typeof node.planning === 'object' &&
66
+ !Array.isArray(node.planning)
67
+ ? /** @type {Record<string, unknown>} */ (node.planning).webScoping
68
+ : undefined;
69
+ const fromLegacyMeta = node?.metadata?.planning?.webScoping;
70
+ const v = fromRoot !== undefined ? fromRoot : fromLegacyMeta;
71
+ return canonicalizeStoredWebScopingPlanning(v);
72
+ }
73
+
74
+ /**
75
+ * Resolved mode for UI, information flow, and focus.
76
+ *
77
+ * Precedence:
78
+ * 1. Explicit design declaration (`planning.webScoping` on the node).
79
+ * 2. Runtime intent (non-empty `taskConfiguration.aiTaskProfile.webQueryTemplate` via {@link readNodeWebScopeEnabled})
80
+ * → treated as `ai-decision` when no explicit design declaration is present.
81
+ * 3. Default → `never`.
82
+ *
83
+ * Narrix `enableWebScope` on the node is not read (graph-engine 5.0+ uses `taskConfiguration` only).
84
+ *
85
+ * @param {object|null|undefined} node
86
+ * @returns {{ mode: WebScopingPlanningMode, source: WebScopingPlanningSource }}
87
+ */
88
+ export function getResolvedWebScopingPlanning(node) {
89
+ const declared = getDeclaredWebScopingPlanning(node);
90
+ if (declared != null) {
91
+ return { mode: declared, source: 'declared' };
92
+ }
93
+ if (readNodeWebScopeEnabled(node)) {
94
+ return { mode: WEB_SCOPING_PLANNING_MODES.aiDecision, source: 'aiTaskProfile-fallback' };
95
+ }
96
+ return { mode: WEB_SCOPING_PLANNING_MODES.never, source: 'default' };
97
+ }
98
+
99
+ /**
100
+ * @param {unknown} mode
101
+ * @returns {string}
102
+ */
103
+ export function formatWebScopingPlanningLabel(mode) {
104
+ const c = canonicalizeStoredWebScopingPlanning(mode) ?? WEB_SCOPING_PLANNING_MODES.never;
105
+ if (c === 'always') return 'Web scope: always';
106
+ if (c === 'ai-decision') return 'Web scope: AI-decision';
107
+ return 'Web scope: never';
108
+ }
109
+
110
+ /**
111
+ * Flow / summary helpers: `always` and `ai-decision` participate in “intent” surfaces.
112
+ * Accepts legacy stored modes for robustness.
113
+ *
114
+ * @param {string|null|undefined} mode
115
+ * @returns {boolean}
116
+ */
117
+ export function isWebScopingPlanningIntentMode(mode) {
118
+ const c = canonicalizeStoredWebScopingPlanning(mode);
119
+ return c === 'always' || c === 'ai-decision';
120
+ }
121
+
122
+ /**
123
+ * Whether this node should appear in "web scoping intent" surfaces.
124
+ * @param {object|null|undefined} node
125
+ * @returns {boolean}
126
+ */
127
+ export function hasWebScopingPlanningIntent(node) {
128
+ if (!supportsWebScopingPlanning(node)) return false;
129
+ const { mode } = getResolvedWebScopingPlanning(node);
130
+ return isWebScopingPlanningIntentMode(mode);
131
+ }