@sapiom/harness 0.8.9 → 0.9.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/CHANGELOG.md +63 -0
- package/README.md +11 -1
- package/dist/core/agent-project-discovery.d.ts +235 -1
- package/dist/core/agent-project-discovery.d.ts.map +1 -1
- package/dist/core/agent-project-discovery.js +303 -1
- package/dist/core/agent-project-discovery.js.map +1 -1
- package/dist/core/canvas-interconnections.d.ts +31 -0
- package/dist/core/canvas-interconnections.d.ts.map +1 -1
- package/dist/core/canvas-interconnections.js +294 -21
- package/dist/core/canvas-interconnections.js.map +1 -1
- package/dist/core/canvas-render.d.ts +32 -0
- package/dist/core/canvas-render.d.ts.map +1 -1
- package/dist/core/canvas-render.js +50 -41
- package/dist/core/canvas-render.js.map +1 -1
- package/dist/core/definition-name.d.ts +12 -0
- package/dist/core/definition-name.d.ts.map +1 -1
- package/dist/core/definition-name.js +14 -5
- package/dist/core/definition-name.js.map +1 -1
- package/dist/core/system-graph-inventory.d.ts +68 -0
- package/dist/core/system-graph-inventory.d.ts.map +1 -0
- package/dist/core/system-graph-inventory.js +312 -0
- package/dist/core/system-graph-inventory.js.map +1 -0
- package/dist/core/system-graph-relationships.d.ts +41 -0
- package/dist/core/system-graph-relationships.d.ts.map +1 -0
- package/dist/core/system-graph-relationships.js +98 -0
- package/dist/core/system-graph-relationships.js.map +1 -0
- package/dist/core/system-graph-store.d.ts +45 -0
- package/dist/core/system-graph-store.d.ts.map +1 -0
- package/dist/core/system-graph-store.js +222 -0
- package/dist/core/system-graph-store.js.map +1 -0
- package/dist/core/system-graph-watcher.d.ts +51 -0
- package/dist/core/system-graph-watcher.d.ts.map +1 -0
- package/dist/core/system-graph-watcher.js +448 -0
- package/dist/core/system-graph-watcher.js.map +1 -0
- package/dist/core/system-graph.d.ts +43 -0
- package/dist/core/system-graph.d.ts.map +1 -0
- package/dist/core/system-graph.js +321 -0
- package/dist/core/system-graph.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +84 -6
- package/dist/core/workflow-registry.d.ts.map +1 -1
- package/dist/core/workflow-registry.js +191 -91
- package/dist/core/workflow-registry.js.map +1 -1
- package/dist/core/workspace-watcher.d.ts +21 -13
- package/dist/core/workspace-watcher.d.ts.map +1 -1
- package/dist/core/workspace-watcher.js +79 -81
- package/dist/core/workspace-watcher.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/agent-move.d.ts +134 -0
- package/dist/server/agent-move.d.ts.map +1 -0
- package/dist/server/agent-move.js +343 -0
- package/dist/server/agent-move.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +275 -22
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest.d.ts +3 -0
- package/dist/server/rest.d.ts.map +1 -1
- package/dist/server/rest.js +3 -0
- package/dist/server/rest.js.map +1 -1
- package/dist/server/studio-rail.d.ts +76 -0
- package/dist/server/studio-rail.d.ts.map +1 -0
- package/dist/server/studio-rail.js +234 -0
- package/dist/server/studio-rail.js.map +1 -0
- package/dist/server/system-graph.d.ts +14 -0
- package/dist/server/system-graph.d.ts.map +1 -0
- package/dist/server/system-graph.js +40 -0
- package/dist/server/system-graph.js.map +1 -0
- package/dist/server/workflow-graph.d.ts +43 -0
- package/dist/server/workflow-graph.d.ts.map +1 -0
- package/dist/server/workflow-graph.js +200 -0
- package/dist/server/workflow-graph.js.map +1 -0
- package/dist/shared/system-graph.d.ts +59 -0
- package/dist/shared/system-graph.d.ts.map +1 -0
- package/dist/shared/system-graph.js +76 -0
- package/dist/shared/system-graph.js.map +1 -0
- package/dist/shared/types.d.ts +45 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/web/assets/index-2Fab6dzf.js +508 -0
- package/dist/web/assets/index-D0doaFvT.css +32 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -5
- package/dist/web/assets/index-B18B2y5o.js +0 -494
- package/dist/web/assets/index-jGBbXx9R.css +0 -32
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.sapiom/studio-rail.json` — the Group axis's stored arrangement, one file
|
|
3
|
+
* per project root (SAP-2929; design.md § Persistence).
|
|
4
|
+
*
|
|
5
|
+
* This router is deliberately DUMB about the file's meaning. It reads, writes
|
|
6
|
+
* and removes an opaque UTF-8 blob; every rule about what the blob means —
|
|
7
|
+
* defensive parsing, pruning member paths no agent claims, and above all the
|
|
8
|
+
* difference between `groups: null` ("nothing stored, detection owns this") and
|
|
9
|
+
* `groups: []` ("the user materialized groups and then deleted them all") —
|
|
10
|
+
* lives in ONE place, `web/src/lib/agent-groups.ts`, where it is unit-tested.
|
|
11
|
+
*
|
|
12
|
+
* That split is the point. In the reference prototype the null/empty
|
|
13
|
+
* distinction was collapsed by a second, well-meaning serializer on the write
|
|
14
|
+
* path: the first page load wrote `groups: []` for a state that was `null`, and
|
|
15
|
+
* from the second load onward every agent fell into `Ungrouped`, in every
|
|
16
|
+
* project, permanently. A server that re-encodes the shape is a second place
|
|
17
|
+
* for that to happen. So the wire format here is `{ raw }` — the exact text —
|
|
18
|
+
* and "un-materialized" reaches disk as DELETE, never as a write.
|
|
19
|
+
*
|
|
20
|
+
* Containment: `root` must be a project root the studio actually knows about
|
|
21
|
+
* (`listKnownRoots`, injected — recentDirs, the configured project root, and
|
|
22
|
+
* live session cwds, which is exactly the set `web/src/lib/project-tree.ts`
|
|
23
|
+
* turns into project rows). Resolution happens before any disk access, so this
|
|
24
|
+
* route cannot be turned into an arbitrary-path file reader or writer. The
|
|
25
|
+
* written path is confined under `<root>/.sapiom/` by `resolveWithinRoot`, so a
|
|
26
|
+
* symlinked `.sapiom` cannot land the file outside the project.
|
|
27
|
+
*
|
|
28
|
+
* Mounted under the same `/api` boot-token middleware as the rest of the REST
|
|
29
|
+
* surface (server/index.ts).
|
|
30
|
+
*/
|
|
31
|
+
import * as path from "node:path";
|
|
32
|
+
import * as fs from "node:fs/promises";
|
|
33
|
+
import { Router } from "express";
|
|
34
|
+
import { detectWorkflowLaunches } from "../core/canvas-interconnections.js";
|
|
35
|
+
import { hasTraversalSegment, resolveWithinRoot } from "../core/path-safety.js";
|
|
36
|
+
/** The directory the file lives in: already a live convention (canvas renders,
|
|
37
|
+
* harness-context.json) and already skipped by every source scan. */
|
|
38
|
+
const SAPIOM_DIR = ".sapiom";
|
|
39
|
+
export const STUDIO_RAIL_FILE = "studio-rail.json";
|
|
40
|
+
/**
|
|
41
|
+
* A stored arrangement is a handful of ids, labels and absolute paths. The cap
|
|
42
|
+
* is a backstop against a client (or a hand edit) turning a preferences file
|
|
43
|
+
* into a place to park a megabyte, not a real limit on how many groups a
|
|
44
|
+
* project may have.
|
|
45
|
+
*/
|
|
46
|
+
export const MAX_STUDIO_RAIL_BYTES = 256 * 1024;
|
|
47
|
+
/** Trailing separators are spelling, not identity — `<root>/` and `<root>`
|
|
48
|
+
* are one directory, and the client stores whichever the user typed. */
|
|
49
|
+
const canonical = (p) => {
|
|
50
|
+
const resolved = path.resolve(p);
|
|
51
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The absolute, resolved root when `candidate` is a project root the studio
|
|
55
|
+
* knows about — null otherwise, which every route answers as a 400 rather than
|
|
56
|
+
* reaching for the disk.
|
|
57
|
+
*/
|
|
58
|
+
function resolveKnownRoot(candidate, knownRoots) {
|
|
59
|
+
if (typeof candidate !== "string" || candidate.trim() === "")
|
|
60
|
+
return null;
|
|
61
|
+
if (hasTraversalSegment(candidate))
|
|
62
|
+
return null;
|
|
63
|
+
if (!path.isAbsolute(candidate))
|
|
64
|
+
return null;
|
|
65
|
+
const wanted = canonical(candidate);
|
|
66
|
+
return knownRoots.some((root) => canonical(root) === wanted)
|
|
67
|
+
? path.resolve(candidate)
|
|
68
|
+
: null;
|
|
69
|
+
}
|
|
70
|
+
/** `<root>/.sapiom/studio-rail.json`, or null if that escapes `root`. */
|
|
71
|
+
export function studioRailPath(root) {
|
|
72
|
+
return resolveWithinRoot(root, path.join(SAPIOM_DIR, STUDIO_RAIL_FILE));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The stored blob, or null.
|
|
76
|
+
*
|
|
77
|
+
* An absent file, an unreadable directory and an oversized file all read as
|
|
78
|
+
* null — "nothing stored", which the model turns into the derived groups. A
|
|
79
|
+
* rail that throws on a bad preferences file is a rail you cannot open in order
|
|
80
|
+
* to fix it.
|
|
81
|
+
*/
|
|
82
|
+
export async function readStudioRailFile(root) {
|
|
83
|
+
const file = studioRailPath(root);
|
|
84
|
+
if (file == null)
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
const stat = await fs.stat(file);
|
|
88
|
+
if (!stat.isFile() || stat.size > MAX_STUDIO_RAIL_BYTES)
|
|
89
|
+
return null;
|
|
90
|
+
return await fs.readFile(file, "utf8");
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Writes the blob verbatim, creating `.sapiom/` if it is not there yet. */
|
|
97
|
+
export async function writeStudioRailFile(root, raw) {
|
|
98
|
+
const file = studioRailPath(root);
|
|
99
|
+
if (file == null)
|
|
100
|
+
throw new Error("studio-rail path escapes the project root");
|
|
101
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
102
|
+
await fs.writeFile(file, raw, "utf8");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Removes the file. Missing is success: "there is no stored arrangement" is the
|
|
106
|
+
* requested end state, and DELETE is how an un-materialized state — including
|
|
107
|
+
* the one `Reset to detected` produces — reaches disk. Skipping the write
|
|
108
|
+
* instead would let the old arrangement outlive the reset.
|
|
109
|
+
*/
|
|
110
|
+
export async function removeStudioRailFile(root) {
|
|
111
|
+
const file = studioRailPath(root);
|
|
112
|
+
if (file == null)
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
await fs.rm(file);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// ENOENT and a read-only checkout alike: nothing stored is the goal state.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Launch edges across every registered agent, as parent-name to child-slug.
|
|
123
|
+
*
|
|
124
|
+
* Uses the shared syntax-only extractor (`core/canvas-interconnections.ts`) —
|
|
125
|
+
* the same detector the canvas and system graph use for agent relationships. A
|
|
126
|
+
* second edge detector would be a second answer to "what does this launch",
|
|
127
|
+
* and the Group axis and system graph are supposed to read one graph.
|
|
128
|
+
*
|
|
129
|
+
* `knownStepIds` is empty on purpose: that argument exists so a capability chip
|
|
130
|
+
* can be attributed to the step it sits in, and a group does not care which
|
|
131
|
+
* step launched the child, only that the launch exists.
|
|
132
|
+
*
|
|
133
|
+
* `child` is the `definition` slug as written at the call site. Matching it to
|
|
134
|
+
* an agent is the client's job (a slug can name an agent by `definitionSlug` or
|
|
135
|
+
* by folder name); the client ignores an unmatched slug.
|
|
136
|
+
*/
|
|
137
|
+
export async function detectLaunchEdges(workflows) {
|
|
138
|
+
const edges = [];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
for (const workflow of workflows) {
|
|
141
|
+
let launches;
|
|
142
|
+
try {
|
|
143
|
+
launches = await detectWorkflowLaunches(workflow.path, new Set());
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
for (const launch of launches) {
|
|
149
|
+
// A project that launches the same definition from three steps is ONE
|
|
150
|
+
// edge: the group is about the relationship, not its multiplicity.
|
|
151
|
+
const key = JSON.stringify([workflow.name, launch.slug]);
|
|
152
|
+
if (launch.slug === workflow.name || seen.has(key))
|
|
153
|
+
continue;
|
|
154
|
+
seen.add(key);
|
|
155
|
+
edges.push({ parent: workflow.name, child: launch.slug });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return edges;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* GET /api/studio-rail?root=abs -> { root, raw: string | null }
|
|
162
|
+
* PUT /api/studio-rail?root=abs { raw } -> { ok: true }
|
|
163
|
+
* DELETE /api/studio-rail?root=abs -> { ok: true }
|
|
164
|
+
* GET /api/studio-rail/launch-edges -> { edges: [{ parent, child }] }
|
|
165
|
+
*/
|
|
166
|
+
export function createStudioRailRouter(deps) {
|
|
167
|
+
const router = Router();
|
|
168
|
+
// BEFORE the `?root=` routes: `/api/studio-rail/launch-edges` carries no
|
|
169
|
+
// root, and a root-less request must never be answered with a file read.
|
|
170
|
+
router.get("/api/studio-rail/launch-edges", async (_req, res, next) => {
|
|
171
|
+
try {
|
|
172
|
+
const edges = await detectLaunchEdges(deps.listWorkflows());
|
|
173
|
+
res.json({ edges });
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
next(err);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
router.get("/api/studio-rail", async (req, res, next) => {
|
|
180
|
+
const root = resolveKnownRoot(req.query.root, await deps.listKnownRoots());
|
|
181
|
+
if (root == null) {
|
|
182
|
+
res.status(400).json({ error: "root must be a known project root" });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
res.json({
|
|
187
|
+
root,
|
|
188
|
+
raw: await readStudioRailFile(root),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
next(err);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
router.put("/api/studio-rail", async (req, res, next) => {
|
|
196
|
+
const root = resolveKnownRoot(req.query.root, await deps.listKnownRoots());
|
|
197
|
+
if (root == null) {
|
|
198
|
+
res.status(400).json({ error: "root must be a known project root" });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const raw = req.body?.raw;
|
|
202
|
+
if (typeof raw !== "string") {
|
|
203
|
+
res.status(400).json({ error: "raw must be a string" });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_STUDIO_RAIL_BYTES) {
|
|
207
|
+
res.status(413).json({ error: "studio-rail.json is too large" });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
await writeStudioRailFile(root, raw);
|
|
212
|
+
res.json({ ok: true });
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
next(err);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
router.delete("/api/studio-rail", async (req, res, next) => {
|
|
219
|
+
const root = resolveKnownRoot(req.query.root, await deps.listKnownRoots());
|
|
220
|
+
if (root == null) {
|
|
221
|
+
res.status(400).json({ error: "root must be a known project root" });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
await removeStudioRailFile(root);
|
|
226
|
+
res.json({ ok: true });
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
next(err);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return router;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=studio-rail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-rail.js","sourceRoot":"","sources":["../../src/server/studio-rail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import type { WorkspaceScope, WorkspaceScopeResolver } from "../core/system-graph.js";
|
|
3
|
+
import type { SystemGraphStore } from "../core/system-graph-store.js";
|
|
4
|
+
import { type SystemGraphSnapshot } from "../shared/system-graph.js";
|
|
5
|
+
export interface SystemGraphRouterOptions {
|
|
6
|
+
scopeResolver: WorkspaceScopeResolver;
|
|
7
|
+
store: SystemGraphStore;
|
|
8
|
+
onScopeAccess?: (scope: WorkspaceScope) => void | Promise<void>;
|
|
9
|
+
/** Re-run registry prerequisites before an explicit user retry. */
|
|
10
|
+
onScopeRefresh?: (scope: WorkspaceScope) => SystemGraphSnapshot | Promise<SystemGraphSnapshot>;
|
|
11
|
+
}
|
|
12
|
+
/** Mounted beneath the boot-token-protected `/api` boundary. */
|
|
13
|
+
export declare function createSystemGraphRouter(options: SystemGraphRouterOptions): Router;
|
|
14
|
+
//# sourceMappingURL=system-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-graph.d.ts","sourceRoot":"","sources":["../../src/server/system-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EAIP,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,mEAAmE;IACnE,cAAc,CAAC,EAAE,CACf,KAAK,EAAE,cAAc,KAClB,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACzD;AAED,gEAAgE;AAChE,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,MAAM,CA4CR"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Router, } from "express";
|
|
2
|
+
import { SYSTEM_GRAPH_CACHE_HEADER, } from "../shared/system-graph.js";
|
|
3
|
+
/** Mounted beneath the boot-token-protected `/api` boundary. */
|
|
4
|
+
export function createSystemGraphRouter(options) {
|
|
5
|
+
const router = Router();
|
|
6
|
+
const route = "/workspaces/:workspaceKey/system-graph";
|
|
7
|
+
const serve = async (req, res, next, refresh) => {
|
|
8
|
+
try {
|
|
9
|
+
const scope = await options.scopeResolver.resolve(req.params.workspaceKey);
|
|
10
|
+
if (!scope) {
|
|
11
|
+
res.status(404).json({ error: "Workspace not found" });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
await options.onScopeAccess?.(scope);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Watcher setup is best-effort. A graph read must remain available
|
|
19
|
+
// even when automatic freshness cannot be armed.
|
|
20
|
+
}
|
|
21
|
+
const snapshot = refresh
|
|
22
|
+
? await (options.onScopeRefresh?.(scope) ??
|
|
23
|
+
options.store.refresh(scope))
|
|
24
|
+
: await options.store.get(scope);
|
|
25
|
+
const cacheStatus = snapshot.state === "ready" ? "complete" : "degraded";
|
|
26
|
+
res.set(SYSTEM_GRAPH_CACHE_HEADER, cacheStatus).json(snapshot);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
next(err);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
router.get(route, (req, res, next) => {
|
|
33
|
+
void serve(req, res, next, false);
|
|
34
|
+
});
|
|
35
|
+
router.post(`${route}/refresh`, (req, res, next) => {
|
|
36
|
+
void serve(req, res, next, true);
|
|
37
|
+
});
|
|
38
|
+
return router;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=system-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-graph.js","sourceRoot":"","sources":["../../src/server/system-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,GAIP,MAAM,SAAS,CAAC;AAOjB,OAAO,EACL,yBAAyB,GAG1B,MAAM,2BAA2B,CAAC;AAYnC,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CACrC,OAAiC;IAEjC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,wCAAwC,CAAC;IAEvD,MAAM,KAAK,GAAG,KAAK,EACjB,GAAY,EACZ,GAAa,EACb,IAAkB,EAClB,OAAgB,EACD,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,OAAO,CAC/C,GAAG,CAAC,MAAM,CAAC,YAAY,CACxB,CAAC;YACF,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;gBACvD,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,mEAAmE;gBACnE,iDAAiD;YACnD,CAAC;YACD,MAAM,QAAQ,GAAG,OAAO;gBACtB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC;oBACpC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACjC,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,WAAW,GACf,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;YACvD,GAAG,CAAC,GAAG,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACnC,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACjD,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Router as ExpressRouter } from "express";
|
|
2
|
+
import { type AgentProjectMarkerInspection } from "../core/agent-project-discovery.js";
|
|
3
|
+
import { deriveWorkflowCanvas, type RenderableWorkflow } from "../core/canvas-render.js";
|
|
4
|
+
import type { CanvasGraph } from "../core/canvas-graph.js";
|
|
5
|
+
import type { CanvasEnrichment } from "../core/canvas-enrichment.js";
|
|
6
|
+
/** Why a board is not a graph. `ok` is the only status carrying one. */
|
|
7
|
+
export type WorkflowGraphStatus = "ok" | "empty" | "preparing" | "error";
|
|
8
|
+
export interface WorkflowGraphResponse {
|
|
9
|
+
/** The resolved absolute agent directory the board was derived from. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** The registry's display name — the board's panel title. */
|
|
12
|
+
name: string;
|
|
13
|
+
status: WorkflowGraphStatus;
|
|
14
|
+
/** The extracted graph; null for every status but "ok". */
|
|
15
|
+
graph: CanvasGraph | null;
|
|
16
|
+
/** Deterministic enrichment derived from `graph`; null when there is none. */
|
|
17
|
+
enrichment: CanvasEnrichment | null;
|
|
18
|
+
/** Human-readable explanation for "empty"/"error"; null otherwise. */
|
|
19
|
+
reason: string | null;
|
|
20
|
+
/** True when the graph came from the extraction cache — no child process ran. */
|
|
21
|
+
cached: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The finished canvas document, byte-identical to what `/canvas/:sessionId/`
|
|
24
|
+
* serves for a session bound to this workflow. Present for EVERY status —
|
|
25
|
+
* an empty board is still a renderable page, not a hole.
|
|
26
|
+
*/
|
|
27
|
+
document: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WorkflowGraphRouterDeps {
|
|
30
|
+
/**
|
|
31
|
+
* Resolves an absolute agent path against the live registry; null when the
|
|
32
|
+
* path is not registered. Called before anything touches disk.
|
|
33
|
+
*/
|
|
34
|
+
resolveWorkflow(agentPath: string): RenderableWorkflow | null;
|
|
35
|
+
/** Seam for tests — defaults to the real `sapiom.json` inspection. */
|
|
36
|
+
inspectMarker?(dir: string): Promise<AgentProjectMarkerInspection>;
|
|
37
|
+
/** Seam for tests — defaults to the real derivation (which runs esbuild). */
|
|
38
|
+
deriveCanvas?: typeof deriveWorkflowCanvas;
|
|
39
|
+
/** Seam for tests — defaults to `fs.realpath`. */
|
|
40
|
+
realpath?(p: string): Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createWorkflowGraphRouter(deps: WorkflowGraphRouterDeps): ExpressRouter;
|
|
43
|
+
//# sourceMappingURL=workflow-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-graph.d.ts","sourceRoot":"","sources":["../../src/server/workflow-graph.ts"],"names":[],"mappings":"AA4DA,OAAO,EAAU,KAAK,MAAM,IAAI,aAAa,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,wEAAwE;AACxE,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC;AAEzE,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2DAA2D;IAC3D,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,8EAA8E;IAC9E,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACpC,sEAAsE;IACtE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iFAAiF;IACjF,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IAC9D,sEAAsE;IACtE,aAAa,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACnE,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,oBAAoB,CAAC;IAC3C,kDAAkD;IAClD,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACvC;AAsDD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,uBAAuB,GAAG,aAAa,CA4FtF"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/workflows/:path/graph` — the WORKFLOW-keyed canvas route (IA-01).
|
|
3
|
+
*
|
|
4
|
+
* The canvas has only ever been reachable through a session: boards live at
|
|
5
|
+
* `/canvas/:harnessSessionId/` and resolve by the session's current binding
|
|
6
|
+
* (server/canvas.ts), so an agent that has never hosted a session has no board
|
|
7
|
+
* at all. Reading agent F's board while working in agent B's session is
|
|
8
|
+
* therefore impossible, which is what the selection-driven rail needs.
|
|
9
|
+
*
|
|
10
|
+
* This route is the second, session-free entry point onto the SAME derivation.
|
|
11
|
+
* It renders nothing new: it calls `deriveWorkflowCanvas` (core/canvas-render.ts),
|
|
12
|
+
* the exact pipeline the write path uses, so `document` here is byte-identical
|
|
13
|
+
* to the render file a bound session's canvas serves for the same workflow.
|
|
14
|
+
* Nothing is written to disk.
|
|
15
|
+
*
|
|
16
|
+
* Keying: `:path` is the agent's absolute directory path, URI-encoded into one
|
|
17
|
+
* segment — `encodeURIComponent(agentPath)`, the same convention
|
|
18
|
+
* `/api/workflows/:id/input-contract` and `/api/workflows/:id/deploy` already
|
|
19
|
+
* use (web/src/lib/api.ts). Express matches on the raw path and decodes the
|
|
20
|
+
* param, so an encoded `/` never splits the route.
|
|
21
|
+
*
|
|
22
|
+
* Containment: the request string is a LOOKUP KEY AND NOTHING ELSE. It is
|
|
23
|
+
* matched against the live registry and then dropped; every filesystem call
|
|
24
|
+
* below runs on `workflow.path` — the path the registry itself authored when
|
|
25
|
+
* it scanned the disk. So the barrier is not "we checked the client's string
|
|
26
|
+
* carefully", it is "the client's string never reaches `fs`", which survives a
|
|
27
|
+
* future edit that reorders the checks and is a barrier static analysis can
|
|
28
|
+
* see rather than one it has to be told about.
|
|
29
|
+
*
|
|
30
|
+
* The lexical guards below (`..` segment, absolute-only) stay: they reject the
|
|
31
|
+
* escape shapes outright rather than let normalization quietly land them on
|
|
32
|
+
* some other registered path. Defence in depth, not the barrier. The marker's
|
|
33
|
+
* realpath is likewise confined to the agent directory, so a symlinked
|
|
34
|
+
* `sapiom.json` cannot turn this into a file reader.
|
|
35
|
+
*
|
|
36
|
+
* Failure modes are deliberately distinct, and every one of them is
|
|
37
|
+
* distinguishable from "no such route":
|
|
38
|
+
*
|
|
39
|
+
* 400 the path is missing, relative, or carries a `..` segment; or
|
|
40
|
+
* `sapiom.json` resolves outside the agent directory
|
|
41
|
+
* 404 the path is not a registered workflow
|
|
42
|
+
* 200 `status: "empty"` — registered, but no readable/parseable
|
|
43
|
+
* `sapiom.json` (absent ⇒ empty, not an error)
|
|
44
|
+
* 200 `status: "preparing"`— dependencies not installed yet
|
|
45
|
+
* 200 `status: "error"` — extraction ran and failed; `reason` says why
|
|
46
|
+
* 200 `status: "ok"` — graph + document
|
|
47
|
+
*
|
|
48
|
+
* EVERY failure below the registry lookup lands in that `error` shape. The
|
|
49
|
+
* derivation spawns a child process, parses a manifest this process did not
|
|
50
|
+
* write, and walks user directories that can EACCES — so it throws, and
|
|
51
|
+
* Express 4 does not catch a rejected async handler. Unhandled, Node's default
|
|
52
|
+
* `--unhandled-rejections=throw` takes the harness process down and the
|
|
53
|
+
* request is never answered: the canvas spins forever and then the studio
|
|
54
|
+
* dies. A board that cannot be derived is a board with a reason on it.
|
|
55
|
+
*
|
|
56
|
+
* Mounted under the same `/api` boot-token middleware as the rest of the REST
|
|
57
|
+
* surface (server/index.ts).
|
|
58
|
+
*/
|
|
59
|
+
import * as path from "node:path";
|
|
60
|
+
import * as fsp from "node:fs/promises";
|
|
61
|
+
import { Router } from "express";
|
|
62
|
+
import { inspectAgentProjectMarker, } from "../core/agent-project-discovery.js";
|
|
63
|
+
import { renderCanvasMessageDocument } from "../core/canvas-template.js";
|
|
64
|
+
import { deriveWorkflowCanvas } from "../core/canvas-render.js";
|
|
65
|
+
import { hasTraversalSegment, resolveWithinRoot } from "../core/path-safety.js";
|
|
66
|
+
/** Why a registered agent has no graph, phrased for a person reading the pane. */
|
|
67
|
+
const EMPTY_REASONS = {
|
|
68
|
+
gone: "This agent's directory is no longer on disk.",
|
|
69
|
+
absent: "This agent has no sapiom.json, so there is no graph to render yet.",
|
|
70
|
+
invalid: "This agent's sapiom.json is not valid JSON, so its graph can't be read.",
|
|
71
|
+
unreadable: "This agent's sapiom.json could not be read.",
|
|
72
|
+
};
|
|
73
|
+
/** The empty board — the same message document server/canvas.ts serves, with
|
|
74
|
+
* the specific reason as its subtitle so the pane is never mutely blank. */
|
|
75
|
+
function emptyResponse(agentPath, name, reason) {
|
|
76
|
+
return {
|
|
77
|
+
path: agentPath,
|
|
78
|
+
name,
|
|
79
|
+
status: "empty",
|
|
80
|
+
graph: null,
|
|
81
|
+
enrichment: null,
|
|
82
|
+
reason,
|
|
83
|
+
cached: false,
|
|
84
|
+
document: renderCanvasMessageDocument("Nothing rendered yet", reason),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The board a failed derivation gets: the declared `error` status, the reason
|
|
89
|
+
* that was thrown, and a renderable page rather than a hole.
|
|
90
|
+
*
|
|
91
|
+
* 200, not 500. `status` already distinguishes every outcome this route has,
|
|
92
|
+
* the pane renders `document` for all of them, and a 5xx would be read by the
|
|
93
|
+
* SPA's `ApiError` path as "this route is broken" rather than "this one
|
|
94
|
+
* agent's graph could not be extracted" — the same distinction
|
|
95
|
+
* `server/actions.ts` preserves when its input-contract extraction fails.
|
|
96
|
+
*/
|
|
97
|
+
function errorResponse(agentPath, name, reason) {
|
|
98
|
+
return {
|
|
99
|
+
path: agentPath,
|
|
100
|
+
name,
|
|
101
|
+
status: "error",
|
|
102
|
+
graph: null,
|
|
103
|
+
enrichment: null,
|
|
104
|
+
reason,
|
|
105
|
+
cached: false,
|
|
106
|
+
document: renderCanvasMessageDocument("Couldn't render this board", reason),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** The thrown value as one line a person can read in the pane. */
|
|
110
|
+
function failureReason(err) {
|
|
111
|
+
const detail = (err instanceof Error ? err.message : String(err)).trim();
|
|
112
|
+
return `Studio couldn't read this agent's graph: ${detail || "the derivation failed."}`;
|
|
113
|
+
}
|
|
114
|
+
export function createWorkflowGraphRouter(deps) {
|
|
115
|
+
const inspectMarker = deps.inspectMarker ?? inspectAgentProjectMarker;
|
|
116
|
+
const deriveCanvas = deps.deriveCanvas ?? deriveWorkflowCanvas;
|
|
117
|
+
const realpath = deps.realpath ?? ((p) => fsp.realpath(p));
|
|
118
|
+
const router = Router();
|
|
119
|
+
router.get("/api/workflows/:path/graph", async (req, res) => {
|
|
120
|
+
const raw = req.params.path;
|
|
121
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
122
|
+
res.status(400).json({ error: "agent path is required" });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Rejected on the RAW value, before resolution: a `..` climb must be an
|
|
126
|
+
// error, not something normalization silently lands on another registered
|
|
127
|
+
// path. (path-safety.ts's segment-aware test — "a..b" is a normal name.)
|
|
128
|
+
if (hasTraversalSegment(raw)) {
|
|
129
|
+
res.status(400).json({ error: "agent path must not contain a '..' segment" });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!path.isAbsolute(raw)) {
|
|
133
|
+
res.status(400).json({ error: "agent path must be absolute" });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// The lookup, and the LAST use of the request string. `workflow` is the
|
|
137
|
+
// registry's own row, so `workflow.path` is a path this server wrote when
|
|
138
|
+
// it scanned the disk — and that is the value every `fs` call below runs
|
|
139
|
+
// on. The request never reaches the filesystem, resolved or otherwise.
|
|
140
|
+
const workflow = deps.resolveWorkflow(path.resolve(raw));
|
|
141
|
+
if (!workflow) {
|
|
142
|
+
res.status(404).json({ error: "agent not found" });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const agentPath = path.resolve(workflow.path);
|
|
146
|
+
const name = workflow.name || path.basename(agentPath);
|
|
147
|
+
let realDir;
|
|
148
|
+
try {
|
|
149
|
+
realDir = await realpath(agentPath);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Registered but gone (the registry prunes lazily). That is an EMPTY
|
|
153
|
+
// board, not a 404 — 404 means "not registered", and conflating the two
|
|
154
|
+
// would make a consumer show "no such agent" for one it can see listed.
|
|
155
|
+
res.json(emptyResponse(agentPath, name, EMPTY_REASONS.gone));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Symlink guard on the one file this route reads by name. A symlinked
|
|
159
|
+
// agent DIRECTORY is legitimate (the user registered it, and realDir is
|
|
160
|
+
// its target), but a `sapiom.json` symlinked out of the project would make
|
|
161
|
+
// a path-keyed read endpoint into a file reader — refuse that outright.
|
|
162
|
+
const marker = await realpath(path.join(realDir, "sapiom.json")).catch(() => null);
|
|
163
|
+
if (marker !== null && resolveWithinRoot(realDir, marker) === null) {
|
|
164
|
+
res.status(400).json({ error: "sapiom.json resolves outside the agent directory" });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// From here on the work is a child process, a manifest this process did
|
|
168
|
+
// not write, and a walk of user directories. All three throw, and an
|
|
169
|
+
// uncaught rejection in an Express 4 async handler kills the harness
|
|
170
|
+
// instead of answering the request.
|
|
171
|
+
try {
|
|
172
|
+
const inspection = await inspectMarker(realDir);
|
|
173
|
+
if (inspection.status !== "valid") {
|
|
174
|
+
res.json(emptyResponse(agentPath, name, EMPTY_REASONS[inspection.status]));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const derived = await deriveCanvas({
|
|
178
|
+
path: workflow.path,
|
|
179
|
+
name,
|
|
180
|
+
definitionId: workflow.definitionId ?? null,
|
|
181
|
+
activeBuildRunStatus: workflow.activeBuildRunStatus ?? null,
|
|
182
|
+
});
|
|
183
|
+
res.json({
|
|
184
|
+
path: agentPath,
|
|
185
|
+
name,
|
|
186
|
+
status: derived.status,
|
|
187
|
+
graph: derived.graph,
|
|
188
|
+
enrichment: derived.enrichment,
|
|
189
|
+
reason: derived.reason,
|
|
190
|
+
cached: derived.cached,
|
|
191
|
+
document: derived.document,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
res.json(errorResponse(agentPath, name, failureReason(err)));
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return router;
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=workflow-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-graph.js","sourceRoot":"","sources":["../../src/server/workflow-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public contract for the local workspace dependency graph. Graph payloads
|
|
3
|
+
* never carry filesystem roots; WorkspaceScopeSummary only joins AppState's
|
|
4
|
+
* existing cwd-backed folder projection to an opaque key.
|
|
5
|
+
*/
|
|
6
|
+
export type WorkspaceKey = string;
|
|
7
|
+
export type AgentKey = string;
|
|
8
|
+
/**
|
|
9
|
+
* The one browser/server rule for a workspace-relative local AgentKey.
|
|
10
|
+
* Callers receive null when the source is not inside the supplied scope.
|
|
11
|
+
*/
|
|
12
|
+
export declare function workspaceRelativeLocalKey(scopeRoot: string, sourceRoot: string): AgentKey | null;
|
|
13
|
+
/** Internal HTTP metadata; it is deliberately not part of SystemGraph JSON. */
|
|
14
|
+
export declare const SYSTEM_GRAPH_CACHE_HEADER = "X-Sapiom-System-Graph-Cache";
|
|
15
|
+
export type SystemGraphCacheStatus = "complete" | "degraded";
|
|
16
|
+
export interface WorkspaceScopeSummary {
|
|
17
|
+
workspaceKey: WorkspaceKey;
|
|
18
|
+
/** Used only to join the existing workspace-folder projection in AppState. */
|
|
19
|
+
cwd: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SystemGraphNode {
|
|
22
|
+
id: string;
|
|
23
|
+
agentKey: AgentKey;
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
export type AgentInvocationMode = "blocking" | "async";
|
|
27
|
+
export interface SystemGraphEdge {
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
kind: "invokes";
|
|
31
|
+
basis: "static";
|
|
32
|
+
mode: AgentInvocationMode;
|
|
33
|
+
}
|
|
34
|
+
export interface GraphWarning {
|
|
35
|
+
code: "unresolved-target" | "dynamic-target" | "duplicate-edge" | "projection-failed" | "duplicate-agent-key" | "inventory-extraction-failed";
|
|
36
|
+
message: string;
|
|
37
|
+
agentKey?: AgentKey;
|
|
38
|
+
}
|
|
39
|
+
export interface SystemGraph {
|
|
40
|
+
kind: "system";
|
|
41
|
+
scope: {
|
|
42
|
+
kind: "working-tree";
|
|
43
|
+
workspaceKey: WorkspaceKey;
|
|
44
|
+
};
|
|
45
|
+
nodes: SystemGraphNode[];
|
|
46
|
+
edges: SystemGraphEdge[];
|
|
47
|
+
warnings: GraphWarning[];
|
|
48
|
+
}
|
|
49
|
+
export type SystemGraphLifecycleState = "building" | "ready" | "stale" | "degraded";
|
|
50
|
+
/** Path-free lifecycle envelope for one workspace projection. */
|
|
51
|
+
export interface SystemGraphSnapshot {
|
|
52
|
+
workspaceKey: WorkspaceKey;
|
|
53
|
+
/** Monotonic within one server process and workspace. */
|
|
54
|
+
revision: number;
|
|
55
|
+
state: SystemGraphLifecycleState;
|
|
56
|
+
/** Null only before a usable projection exists. */
|
|
57
|
+
graph: SystemGraph | null;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=system-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-graph.d.ts","sourceRoot":"","sources":["../../src/shared/system-graph.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAClC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAuD9B;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,QAAQ,GAAG,IAAI,CA0BjB;AAED,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AACvE,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,UAAU,CAAC;AAE7D,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,YAAY,CAAC;IAC3B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,QAAQ,CAAC;IAChB,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EACA,mBAAmB,GACnB,gBAAgB,GAChB,gBAAgB,GAChB,mBAAmB,GACnB,qBAAqB,GACrB,6BAA6B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE;QACL,IAAI,EAAE,cAAc,CAAC;QACrB,YAAY,EAAE,YAAY,CAAC;KAC5B,CAAC;IACF,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AACD,MAAM,MAAM,yBAAyB,GACjC,UAAU,GACV,OAAO,GACP,OAAO,GACP,UAAU,CAAC;AAEf,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,YAAY,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,yBAAyB,CAAC;IACjC,mDAAmD;IACnD,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3B"}
|