@wairon/cli 5.0.2-dev.8 → 5.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/dist/cli/index.js +2296 -824
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1114 -137
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -148,7 +148,7 @@ var init_domain = __esm({
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
// src/models/project.ts
|
|
151
|
-
var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, ProjectConfigSchema;
|
|
151
|
+
var import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema;
|
|
152
152
|
var init_project = __esm({
|
|
153
153
|
"src/models/project.ts"() {
|
|
154
154
|
"use strict";
|
|
@@ -272,6 +272,24 @@ var init_project = __esm({
|
|
|
272
272
|
/** Base directory containing SDD specification files, relative to project root */
|
|
273
273
|
specsDir: import_zod3.z.string().default(".wai/specs")
|
|
274
274
|
});
|
|
275
|
+
ProfileSelectionSubjectSchema = import_zod3.z.object({
|
|
276
|
+
userId: import_zod3.z.string(),
|
|
277
|
+
kind: import_zod3.z.string(),
|
|
278
|
+
issuer: import_zod3.z.string(),
|
|
279
|
+
externalSubject: import_zod3.z.string().optional(),
|
|
280
|
+
displayName: import_zod3.z.string().optional(),
|
|
281
|
+
email: import_zod3.z.string().optional()
|
|
282
|
+
});
|
|
283
|
+
ProjectProfileSelectionSchema = import_zod3.z.object({
|
|
284
|
+
/** Selected architectural profile ids. The first resolvable one is applied as projectType. */
|
|
285
|
+
profileIds: import_zod3.z.array(import_zod3.z.string()).default([]),
|
|
286
|
+
/** Pack names the governing policy requires for this project. */
|
|
287
|
+
requiredPackNames: import_zod3.z.array(import_zod3.z.string()).default([]),
|
|
288
|
+
/** Pack names applied by default unless explicitly overridden. */
|
|
289
|
+
defaultPackNames: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
290
|
+
selectedBy: ProfileSelectionSubjectSchema.optional(),
|
|
291
|
+
selectedAt: import_zod3.z.string()
|
|
292
|
+
});
|
|
275
293
|
ProjectConfigSchema = import_zod3.z.object({
|
|
276
294
|
/**
|
|
277
295
|
* Schema version — used to detect incompatible config formats in future
|
|
@@ -312,6 +330,13 @@ var init_project = __esm({
|
|
|
312
330
|
useGlobalPacks: import_zod3.z.boolean().default(true)
|
|
313
331
|
}).optional(),
|
|
314
332
|
paths: PathsConfigSchema.default({}),
|
|
333
|
+
/**
|
|
334
|
+
* The profile/pack selection a hosted policy workflow applied to this project.
|
|
335
|
+
* The RECORD of what was chosen; `projectType` above is what actually governs
|
|
336
|
+
* validation. Modeled so the parse/write round trip preserves it (see
|
|
337
|
+
* ProjectProfileSelectionSchema).
|
|
338
|
+
*/
|
|
339
|
+
profileSelection: ProjectProfileSelectionSchema.optional(),
|
|
315
340
|
/**
|
|
316
341
|
* Path to a directory containing org/user-level default templates.
|
|
317
342
|
* Resolved before built-in templates but after project-local templates.
|
|
@@ -785,6 +810,18 @@ var init_specs = __esm({
|
|
|
785
810
|
// Required if type is 'call', references Method name on target interface
|
|
786
811
|
capability: import_zod6.z.string().optional(),
|
|
787
812
|
// Required if type is 'dispatch': the capability routed through the target Portal's dispatch table
|
|
813
|
+
/**
|
|
814
|
+
* call/dispatch only: the credential this step presents to an authed callee
|
|
815
|
+
* Portal, and WHERE it is loaded from (`from`). Two forms: an OPAQUE source
|
|
816
|
+
* (`env:API_KEY`, a config key, `vault:path`, a free note) — a design note
|
|
817
|
+
* wairon never resolves; or a MODELED reference `component:<id>` pointing at
|
|
818
|
+
* the Adapter/Store that provides the secret — validated to resolve, be an
|
|
819
|
+
* Adapter/Store, and be wired to the presenter (a checked graph edge). The
|
|
820
|
+
* actual secret is never stored here. Absence on a call into a Portal whose
|
|
821
|
+
* `auth ≠ none` warns (PORTAL_AUTH_UNMET), so credential loading is never
|
|
822
|
+
* overlooked.
|
|
823
|
+
*/
|
|
824
|
+
auth: import_zod6.z.object({ from: import_zod6.z.string(), note: import_zod6.z.string().optional() }).optional(),
|
|
788
825
|
assertsGuarantees: import_zod6.z.array(GuaranteeSchema).optional(),
|
|
789
826
|
/**
|
|
790
827
|
* Declared entity invariants this step upholds, as "<type-id>.<invariant-id>"
|
|
@@ -1248,6 +1285,47 @@ var init_errors = __esm({
|
|
|
1248
1285
|
}
|
|
1249
1286
|
});
|
|
1250
1287
|
|
|
1288
|
+
// src/core/statehash.ts
|
|
1289
|
+
function computeStateId() {
|
|
1290
|
+
const tree = {
|
|
1291
|
+
system: loadSystemSpec(),
|
|
1292
|
+
subsystems: loadSubsystemSpecs(),
|
|
1293
|
+
components: loadComponentSpecs(),
|
|
1294
|
+
interfaces: loadInterfaceSpecs(),
|
|
1295
|
+
implementations: loadImplementationSpecs(),
|
|
1296
|
+
types: loadTypeSpecs()
|
|
1297
|
+
};
|
|
1298
|
+
const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
1299
|
+
return { algorithm: "sha256", digest };
|
|
1300
|
+
}
|
|
1301
|
+
function stateIdEquals(a, b) {
|
|
1302
|
+
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
1303
|
+
}
|
|
1304
|
+
function canonicalize(value) {
|
|
1305
|
+
return JSON.stringify(sortKeys(value));
|
|
1306
|
+
}
|
|
1307
|
+
function sortKeys(v) {
|
|
1308
|
+
if (Array.isArray(v)) return v.map(sortKeys);
|
|
1309
|
+
if (v && typeof v === "object") {
|
|
1310
|
+
const src = v;
|
|
1311
|
+
const out = {};
|
|
1312
|
+
for (const k of Object.keys(src).sort()) {
|
|
1313
|
+
if (k === "createdAt" || k === "updatedAt") continue;
|
|
1314
|
+
out[k] = sortKeys(src[k]);
|
|
1315
|
+
}
|
|
1316
|
+
return out;
|
|
1317
|
+
}
|
|
1318
|
+
return v;
|
|
1319
|
+
}
|
|
1320
|
+
var crypto;
|
|
1321
|
+
var init_statehash = __esm({
|
|
1322
|
+
"src/core/statehash.ts"() {
|
|
1323
|
+
"use strict";
|
|
1324
|
+
crypto = __toESM(require("crypto"));
|
|
1325
|
+
init_specs2();
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1251
1329
|
// src/core/narrative-labels.ts
|
|
1252
1330
|
function resolveNarrativeLabels(methodName, steps) {
|
|
1253
1331
|
const errors = [];
|
|
@@ -2333,6 +2411,16 @@ header input[type="search"]::placeholder { color:var(--dim); }
|
|
|
2333
2411
|
#moreMenu .dropdown { display:block; width:100%; }
|
|
2334
2412
|
#moreMenu .dropdown > .tbtn, #moreMenu > .tbtn { display:block; width:100%; text-align:left; border:none; background:transparent; margin:2px 0; }
|
|
2335
2413
|
#moreMenu .dropdown > .tbtn:hover, #moreMenu > .tbtn:hover { background:var(--hover-bg); }
|
|
2414
|
+
/* Staged header compaction (responsive only, never persisted): compact
|
|
2415
|
+
stand-ins for the search input, the mode tabs, and the ancestor crumbs.
|
|
2416
|
+
All hidden at full width, so a roomy header renders exactly as before. */
|
|
2417
|
+
#searchBtn { position:relative; }
|
|
2418
|
+
#searchBtn.hasq::after { content:''; position:absolute; top:3px; right:3px; width:7px; height:7px; border-radius:50%; background:var(--accent); }
|
|
2419
|
+
.search-menu { min-width:210px; padding:8px; }
|
|
2420
|
+
.search-menu input[type="search"] { width:100%; }
|
|
2421
|
+
#modeMenu button.active { background:var(--accent); color:#fff; font-weight:700; }
|
|
2422
|
+
#crumbs .dropdown { display:inline-flex; }
|
|
2423
|
+
#crumbs .crumbmore { font-weight:700; }
|
|
2336
2424
|
|
|
2337
2425
|
/* Settings panel \u2014 toggle switches */
|
|
2338
2426
|
.settings-menu { min-width:266px; }
|
|
@@ -2456,9 +2544,17 @@ body.presentation #exitPresent, body.presentation #presentDetails { display:bloc
|
|
|
2456
2544
|
<button data-vm="types">Types</button>
|
|
2457
2545
|
<button data-vm="databases">Databases</button>
|
|
2458
2546
|
</div>
|
|
2547
|
+
<div class="dropdown" id="modeDd" style="display:none">
|
|
2548
|
+
<button class="tbtn" id="modeBtn" title="Switch between the component architecture, the type ERD, or the database schemas">Components \u25BE</button>
|
|
2549
|
+
<div class="menu" id="modeMenu"></div>
|
|
2550
|
+
</div>
|
|
2459
2551
|
<nav id="crumbs"></nav>
|
|
2460
2552
|
<span class="divider"></span>
|
|
2461
2553
|
<input id="search" type="search" placeholder="Search this view\u2026">
|
|
2554
|
+
<div class="dropdown" id="searchDd" style="display:none">
|
|
2555
|
+
<button class="tbtn" id="searchBtn" title="Search this view">\u{1F50D}</button>
|
|
2556
|
+
<div class="menu search-menu" id="searchMenu"></div>
|
|
2557
|
+
</div>
|
|
2462
2558
|
<div class="seg" id="typesDetailSeg" style="display:none" title="ERD detail level">
|
|
2463
2559
|
<button data-td="full">Full</button>
|
|
2464
2560
|
<button data-td="fields">Fields</button>
|
|
@@ -2975,13 +3071,300 @@ var MODEL = __MODEL_JSON__;
|
|
|
2975
3071
|
var BOX_W = 200, BOX_H = 56, SUBBOX_W = 230, SUBBOX_H = 84, GAP_X = 110, GAP_Y = 34;
|
|
2976
3072
|
var INNER_W = 130, INNER_H = 36, INNER_GAPX = 26, INNER_GAPY = 12, HEAD_H = 34, PADI = 14;
|
|
2977
3073
|
|
|
3074
|
+
// ---- deep expansion (opt-in via subsystem.deepInternals) -------------------
|
|
3075
|
+
// When Internals is on and a subsystem record carries deepInternals:true, its
|
|
3076
|
+
// box renders its WHOLE subtree instead of one layer: deep-flagged child
|
|
3077
|
+
// subsystems become NESTED boundary boxes (recursing, defensively capped),
|
|
3078
|
+
// every other child renders as a fixed leaf tile that is never expanded
|
|
3079
|
+
// further. Sizes are computed bottom-up (a nested container tile takes its
|
|
3080
|
+
// recursive {w,h}); positions are emitted top-down by accumulating parent
|
|
3081
|
+
// top-left offsets. Relations between concrete visible endpoints are drawn
|
|
3082
|
+
// as ONE direct line each by buildDeepContext \u2014 crossing nested boundaries
|
|
3083
|
+
// on purpose (the full org overview of project relations); only relations
|
|
3084
|
+
// that cannot resolve to two concrete endpoints keep today's port machinery,
|
|
3085
|
+
// and ONLY at the outermost box. Without the flag this whole path is inert
|
|
3086
|
+
// and the classic one-layer innerLayout runs unchanged.
|
|
3087
|
+
var DEEP_MAX_DEPTH = 6;
|
|
3088
|
+
function isDeepId(subId) {
|
|
3089
|
+
var s = subById[subId];
|
|
3090
|
+
return !!(s && s.deepInternals);
|
|
3091
|
+
}
|
|
3092
|
+
// The DEEPEST visible tile representing compId inside the deep-expanded box
|
|
3093
|
+
// rooted at rootSubId: descend deep-flagged containers (the same expansion
|
|
3094
|
+
// rule as deepContainerLayout) until the containing child is a leaf tile.
|
|
3095
|
+
// Returns the child entry { kind, id } (its node id is IN(kind, id)).
|
|
3096
|
+
function deepLeafFor(compId, rootSubId) {
|
|
3097
|
+
var subId = rootSubId, depth = 1;
|
|
3098
|
+
for (;;) {
|
|
3099
|
+
var child = childOfScopeContaining(compId, { kind: 'subsystem', id: subId });
|
|
3100
|
+
if (!child) return null;
|
|
3101
|
+
if (child.kind === 'subsystem' && isDeepId(child.id) && depth < DEEP_MAX_DEPTH) {
|
|
3102
|
+
subId = child.id; depth += 1;
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
return child;
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
// One deep container's DIRECT children, placed with PER-TILE sizes (nested
|
|
3109
|
+
// containers take their recursive size; leaves stay INNER_W x INNER_H). The
|
|
3110
|
+
// placement mirrors innerLayout's strategy switch, generalised to variable
|
|
3111
|
+
// tile sizes. Tiles are centres relative to THIS container's top-left corner
|
|
3112
|
+
// (content sits right of PADI, below the HEAD_H label band); nested tiles
|
|
3113
|
+
// are relative to their own container, so emission accumulates offsets.
|
|
3114
|
+
function deepContainerLayout(subId, depth) {
|
|
3115
|
+
var kids = childrenOf({ kind: 'subsystem', id: subId });
|
|
3116
|
+
if (!kids.length) {
|
|
3117
|
+
// An EMPTY deep subsystem still shows as a (min-size) boundary box.
|
|
3118
|
+
return { tiles: [], w: INNER_W + 2 * PADI, h: HEAD_H + PADI };
|
|
3119
|
+
}
|
|
3120
|
+
var scope = { kind: 'subsystem', id: subId };
|
|
3121
|
+
var kidKey = function (k) { return k.kind + ':' + k.id; };
|
|
3122
|
+
var size = {};
|
|
3123
|
+
kids.forEach(function (k) {
|
|
3124
|
+
if (k.kind === 'subsystem' && isDeepId(k.id) && depth < DEEP_MAX_DEPTH) {
|
|
3125
|
+
var nested = deepContainerLayout(k.id, depth + 1);
|
|
3126
|
+
size[kidKey(k)] = { w: nested.w, h: nested.h, sub: nested };
|
|
3127
|
+
} else {
|
|
3128
|
+
size[kidKey(k)] = { w: INNER_W, h: INNER_H, sub: null };
|
|
3129
|
+
}
|
|
3130
|
+
});
|
|
3131
|
+
// Intra-container edges lifted to DIRECT children \u2014 for LAYOUT ONLY (the
|
|
3132
|
+
// drawn lines come from buildDeepContext's direct pass, never per level).
|
|
3133
|
+
var intra = {};
|
|
3134
|
+
MODEL.edges.forEach(function (edge) {
|
|
3135
|
+
var a = childOfScopeContaining(edge.from, scope);
|
|
3136
|
+
var b = childOfScopeContaining(edge.to, scope);
|
|
3137
|
+
if (!a || !b) return;
|
|
3138
|
+
var ak = a.kind + ':' + a.id, bk = b.kind + ':' + b.id;
|
|
3139
|
+
if (!size[ak] || !size[bk] || ak === bk) return;
|
|
3140
|
+
intra[ak + '=>' + bk] = 1;
|
|
3141
|
+
});
|
|
3142
|
+
var layer = {};
|
|
3143
|
+
function calc(k, stack) {
|
|
3144
|
+
var key = kidKey(k);
|
|
3145
|
+
if (layer[key] !== undefined) return layer[key];
|
|
3146
|
+
if (stack[key]) return 0;
|
|
3147
|
+
stack[key] = 1;
|
|
3148
|
+
var l = 0;
|
|
3149
|
+
if (k.kind === 'component') {
|
|
3150
|
+
var c = compById[k.id];
|
|
3151
|
+
if (c && (c.componentType === 'Portal' || c.componentType === 'Observer')) { layer[key] = 0; delete stack[key]; return 0; }
|
|
3152
|
+
}
|
|
3153
|
+
Object.keys(intra).forEach(function (ek) {
|
|
3154
|
+
var cut = ek.indexOf('=>');
|
|
3155
|
+
if (ek.slice(cut + 2) !== key) return;
|
|
3156
|
+
var srcKid = kids.filter(function (x) { return kidKey(x) === ek.slice(0, cut); })[0];
|
|
3157
|
+
if (srcKid) l = Math.max(l, calc(srcKid, stack) + 1);
|
|
3158
|
+
});
|
|
3159
|
+
delete stack[key];
|
|
3160
|
+
layer[key] = l;
|
|
3161
|
+
return l;
|
|
3162
|
+
}
|
|
3163
|
+
kids.forEach(function (k) { calc(k, {}); });
|
|
3164
|
+
var tiles = [], contentW = 0, contentH = 0;
|
|
3165
|
+
if (state.layout === 'grid') {
|
|
3166
|
+
// Row packing by ACTUAL tile size (the fixed per-column grid assumed
|
|
3167
|
+
// uniform tiles); the target row width follows the tile count, widened
|
|
3168
|
+
// to at least the widest single tile.
|
|
3169
|
+
var gsorted = kids.slice().sort(function (a, b) { return a.id < b.id ? -1 : 1; });
|
|
3170
|
+
var target = Math.max(1, Math.ceil(Math.sqrt(kids.length))) * (INNER_W + INNER_GAPX);
|
|
3171
|
+
kids.forEach(function (k) { var s0 = size[kidKey(k)]; if (s0.w > target) target = s0.w; });
|
|
3172
|
+
var gx = 0, gy = 0, rowH = 0;
|
|
3173
|
+
gsorted.forEach(function (k) {
|
|
3174
|
+
var s = size[kidKey(k)];
|
|
3175
|
+
if (gx > 0 && gx + s.w > target) { gx = 0; gy += rowH + INNER_GAPY; rowH = 0; }
|
|
3176
|
+
tiles.push({ kid: k, x: gx + s.w / 2, y: gy + s.h / 2, w: s.w, h: s.h, sub: s.sub });
|
|
3177
|
+
gx += s.w + INNER_GAPX;
|
|
3178
|
+
if (s.h > rowH) rowH = s.h;
|
|
3179
|
+
if (gx - INNER_GAPX > contentW) contentW = gx - INNER_GAPX;
|
|
3180
|
+
if (gy + rowH > contentH) contentH = gy + rowH;
|
|
3181
|
+
});
|
|
3182
|
+
} else if (state.layout === 'concentric' || state.layout === 'force') {
|
|
3183
|
+
var ideg = {};
|
|
3184
|
+
kids.forEach(function (k) { ideg[kidKey(k)] = 0; });
|
|
3185
|
+
Object.keys(intra).forEach(function (ek) {
|
|
3186
|
+
var cut2 = ek.indexOf('=>');
|
|
3187
|
+
var sk = ek.slice(0, cut2), tk = ek.slice(cut2 + 2);
|
|
3188
|
+
if (ideg[sk] !== undefined) ideg[sk]++;
|
|
3189
|
+
if (ideg[tk] !== undefined) ideg[tk]++;
|
|
3190
|
+
});
|
|
3191
|
+
var rel = concentricPositions(
|
|
3192
|
+
kids.map(kidKey),
|
|
3193
|
+
function (key) { return ideg[key] || 0; },
|
|
3194
|
+
function (key) { return { w: size[key].w, h: size[key].h }; }
|
|
3195
|
+
);
|
|
3196
|
+
// Normalise by the tiles' BOUNDING BOX (not just the centres) so a wide
|
|
3197
|
+
// nested container on the rim still clears the container's left/top pad.
|
|
3198
|
+
var minL = Infinity, minT = Infinity;
|
|
3199
|
+
kids.forEach(function (k) {
|
|
3200
|
+
var s1 = size[kidKey(k)], p1 = rel[kidKey(k)] || { x: 0, y: 0 };
|
|
3201
|
+
if (p1.x - s1.w / 2 < minL) minL = p1.x - s1.w / 2;
|
|
3202
|
+
if (p1.y - s1.h / 2 < minT) minT = p1.y - s1.h / 2;
|
|
3203
|
+
});
|
|
3204
|
+
if (minL === Infinity) { minL = 0; minT = 0; }
|
|
3205
|
+
kids.forEach(function (k) {
|
|
3206
|
+
var s2 = size[kidKey(k)], p2 = rel[kidKey(k)] || { x: 0, y: 0 };
|
|
3207
|
+
var cx = p2.x - minL, cyy = p2.y - minT;
|
|
3208
|
+
tiles.push({ kid: k, x: cx, y: cyy, w: s2.w, h: s2.h, sub: s2.sub });
|
|
3209
|
+
if (cx + s2.w / 2 > contentW) contentW = cx + s2.w / 2;
|
|
3210
|
+
if (cyy + s2.h / 2 > contentH) contentH = cyy + s2.h / 2;
|
|
3211
|
+
});
|
|
3212
|
+
} else {
|
|
3213
|
+
// Layered dependency columns: the column is as wide as its widest tile,
|
|
3214
|
+
// and each tile advances by ITS OWN height.
|
|
3215
|
+
var cols = {};
|
|
3216
|
+
kids.forEach(function (k) { var l = layer[kidKey(k)] || 0; (cols[l] = cols[l] || []).push(k); });
|
|
3217
|
+
var colKeys = Object.keys(cols).map(Number).sort(function (a, b) { return a - b; });
|
|
3218
|
+
var x = 0;
|
|
3219
|
+
colKeys.forEach(function (ck) {
|
|
3220
|
+
var col = cols[ck].sort(function (a, b) { return a.id < b.id ? -1 : 1; });
|
|
3221
|
+
var colW = 0, y = 0;
|
|
3222
|
+
col.forEach(function (k) { var s3 = size[kidKey(k)]; if (s3.w > colW) colW = s3.w; });
|
|
3223
|
+
col.forEach(function (k) {
|
|
3224
|
+
var s = size[kidKey(k)];
|
|
3225
|
+
tiles.push({ kid: k, x: x + colW / 2, y: y + s.h / 2, w: s.w, h: s.h, sub: s.sub });
|
|
3226
|
+
y += s.h + INNER_GAPY;
|
|
3227
|
+
});
|
|
3228
|
+
if (y - INNER_GAPY > contentH) contentH = y - INNER_GAPY;
|
|
3229
|
+
x += colW + INNER_GAPX;
|
|
3230
|
+
});
|
|
3231
|
+
contentW = x - INNER_GAPX;
|
|
3232
|
+
}
|
|
3233
|
+
tiles.forEach(function (t) { t.x += PADI; t.y += HEAD_H; });
|
|
3234
|
+
return { tiles: tiles, w: contentW + 2 * PADI, h: HEAD_H + contentH + PADI };
|
|
3235
|
+
}
|
|
3236
|
+
// Top-level deep box: the recursive interior plus today's port machinery at
|
|
3237
|
+
// the OUTERMOST box only (buildDeepContext supplies which relations still
|
|
3238
|
+
// need ports; stubs run port <-> the DEEPEST visible leaf tile). Returns the
|
|
3239
|
+
// same shape as innerLayout, plus deep:true so emission recurses.
|
|
3240
|
+
function deepLayout(entry, portRec) {
|
|
3241
|
+
var box = deepContainerLayout(entry.id, 1);
|
|
3242
|
+
var parentId = anchorNodeId(entry);
|
|
3243
|
+
var pBaseIn = 'p~in~' + parentId + '~', pBaseOut = 'p~out~' + parentId + '~';
|
|
3244
|
+
var extIn = portRec ? portRec.extIn : {}, extOut = portRec ? portRec.extOut : {};
|
|
3245
|
+
var inIds = Object.keys(extIn).sort(), outIds = Object.keys(extOut).sort();
|
|
3246
|
+
var hasIn = inIds.length > 0, hasOut = outIds.length > 0;
|
|
3247
|
+
var PROXY_W = 22, PROXY_H = 22, PROXY_GAP = 8;
|
|
3248
|
+
var shift = hasIn ? PROXY_W + INNER_GAPX : 0;
|
|
3249
|
+
var tiles = box.tiles;
|
|
3250
|
+
if (shift) tiles.forEach(function (t) { t.x += shift; });
|
|
3251
|
+
var w = box.w + shift + (hasOut ? PROXY_W + INNER_GAPX : 0);
|
|
3252
|
+
if (w < SUBBOX_W) w = SUBBOX_W;
|
|
3253
|
+
var stackMax = Math.max(inIds.length, outIds.length);
|
|
3254
|
+
var h = Math.max(box.h, HEAD_H + stackMax * PROXY_H + Math.max(0, stackMax - 1) * PROXY_GAP + PADI);
|
|
3255
|
+
var midY = HEAD_H + Math.max(0, (h - HEAD_H - PADI) / 2);
|
|
3256
|
+
function stackPorts(ids, recs, base, cx, dir) {
|
|
3257
|
+
var total = ids.length * PROXY_H + Math.max(0, ids.length - 1) * PROXY_GAP;
|
|
3258
|
+
var y0 = Math.max(HEAD_H + PROXY_H / 2, midY - total / 2 + PROXY_H / 2);
|
|
3259
|
+
return ids.map(function (eid, i) {
|
|
3260
|
+
var km = recs[eid].kids, klist = [];
|
|
3261
|
+
Object.keys(km).forEach(function (key) { klist.push(km[key]); });
|
|
3262
|
+
return { id: base + eid, extId: eid, dir: dir, kids: klist, raws: Object.keys(recs[eid].raws), x: cx, y: y0 + i * (PROXY_H + PROXY_GAP), w: PROXY_W, h: PROXY_H };
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
return {
|
|
3266
|
+
deep: true,
|
|
3267
|
+
tiles: tiles,
|
|
3268
|
+
edges: portRec ? Object.keys(portRec.stubs).map(function (k) { return portRec.stubs[k]; }) : [],
|
|
3269
|
+
proxies: stackPorts(inIds, extIn, pBaseIn, PADI + PROXY_W / 2, 'in')
|
|
3270
|
+
.concat(stackPorts(outIds, extOut, pBaseOut, w - PADI - PROXY_W / 2, 'out')),
|
|
3271
|
+
w: w,
|
|
3272
|
+
h: h,
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
// View-level deep context (null unless Internals is on AND at least one
|
|
3276
|
+
// in-view entry is deep-flagged \u2014 the classic path never sees it): which
|
|
3277
|
+
// top-level entries are deep-expanded; every relation drawn as a DIRECT
|
|
3278
|
+
// concrete line (deduped per src=>tgt pair); the per-top-pair count used to
|
|
3279
|
+
// suppress aggregated edges whose constituents are ALL drawn directly; and
|
|
3280
|
+
// the port records for relations that keep today's port semantics.
|
|
3281
|
+
function buildDeepContext(scope, entries) {
|
|
3282
|
+
if (!state.internals) return null;
|
|
3283
|
+
var deepByAnchor = {}, any = false;
|
|
3284
|
+
entries.forEach(function (e) {
|
|
3285
|
+
if (e.kind === 'subsystem' && isDeepId(e.id)) { deepByAnchor[anchorNodeId(e)] = e; any = true; }
|
|
3286
|
+
});
|
|
3287
|
+
if (!any) return null;
|
|
3288
|
+
var entryByAnchor = {};
|
|
3289
|
+
entries.forEach(function (e) { entryByAnchor[e.kind + ':' + e.id] = e; });
|
|
3290
|
+
// A relation endpoint's DIRECT-line node in this view: the deepest leaf
|
|
3291
|
+
// tile inside a deep-expanded entry, or a top-level component box ITSELF.
|
|
3292
|
+
// null = this endpoint keeps aggregated/port semantics (interiors of
|
|
3293
|
+
// non-deep entries, out-of-scope counterparts).
|
|
3294
|
+
function directEnd(compId) {
|
|
3295
|
+
var child = childOfScopeContaining(compId, scope);
|
|
3296
|
+
var entry = child && entryByAnchor[child.kind + ':' + child.id];
|
|
3297
|
+
if (!entry) return null;
|
|
3298
|
+
var aid = anchorNodeId(entry);
|
|
3299
|
+
if (deepByAnchor[aid]) {
|
|
3300
|
+
var leaf = deepLeafFor(compId, entry.id);
|
|
3301
|
+
return leaf ? { node: IN(leaf.kind, leaf.id), top: aid, deep: true } : null;
|
|
3302
|
+
}
|
|
3303
|
+
if (entry.kind === 'component' && entry.id === compId) return { node: aid, top: aid, deep: false };
|
|
3304
|
+
return null;
|
|
3305
|
+
}
|
|
3306
|
+
var direct = {}, directTopCount = {}, ports = {};
|
|
3307
|
+
function portRec(aid) { return ports[aid] = ports[aid] || { extIn: {}, extOut: {}, stubs: {} }; }
|
|
3308
|
+
MODEL.edges.forEach(function (edge) {
|
|
3309
|
+
var a = directEnd(edge.from), b = directEnd(edge.to);
|
|
3310
|
+
if (a && b && (a.deep || b.deep) && a.node !== b.node) {
|
|
3311
|
+
// Drawn as ONE direct line \u2014 never ALSO as ports/stubs (dedupe rule).
|
|
3312
|
+
var key = a.node + '=>' + b.node;
|
|
3313
|
+
if (!direct[key]) direct[key] = { src: a.node, tgt: b.node, cross: false, aTop: a.top, bTop: b.top };
|
|
3314
|
+
if (edge.cross) direct[key].cross = true;
|
|
3315
|
+
if (a.top !== b.top) {
|
|
3316
|
+
var tk = a.top + '=>' + b.top;
|
|
3317
|
+
directTopCount[tk] = (directTopCount[tk] || 0) + 1;
|
|
3318
|
+
}
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
// Not a direct line: keep today's port semantics on any deep box with
|
|
3322
|
+
// exactly one endpoint inside its subtree, stubbed to the deepest leaf.
|
|
3323
|
+
var ac = childOfScopeContaining(edge.from, scope);
|
|
3324
|
+
var bc = childOfScopeContaining(edge.to, scope);
|
|
3325
|
+
var aEnt = ac && entryByAnchor[ac.kind + ':' + ac.id];
|
|
3326
|
+
var bEnt = bc && entryByAnchor[bc.kind + ':' + bc.id];
|
|
3327
|
+
var aAid = aEnt ? anchorNodeId(aEnt) : null;
|
|
3328
|
+
var bAid = bEnt ? anchorNodeId(bEnt) : null;
|
|
3329
|
+
if (aAid === bAid) return; // internal to one entry, or neither in scope
|
|
3330
|
+
if (aAid && deepByAnchor[aAid]) {
|
|
3331
|
+
var leafA = deepLeafFor(edge.from, aEnt.id);
|
|
3332
|
+
if (leafA) {
|
|
3333
|
+
var recA = portRec(aAid);
|
|
3334
|
+
var ro = recA.extOut[edge.to] = recA.extOut[edge.to] || { kids: {}, raws: {} };
|
|
3335
|
+
ro.kids[leafA.kind + ':' + leafA.id] = leafA;
|
|
3336
|
+
ro.raws[edge.from] = 1;
|
|
3337
|
+
var poId = 'p~out~' + aAid + '~' + edge.to;
|
|
3338
|
+
recA.stubs[IN(leafA.kind, leafA.id) + '=>' + poId] = { src: IN(leafA.kind, leafA.id), tgt: poId, stub: true };
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (bAid && deepByAnchor[bAid]) {
|
|
3342
|
+
var leafB = deepLeafFor(edge.to, bEnt.id);
|
|
3343
|
+
if (leafB) {
|
|
3344
|
+
var recB = portRec(bAid);
|
|
3345
|
+
var ri = recB.extIn[edge.from] = recB.extIn[edge.from] || { kids: {}, raws: {} };
|
|
3346
|
+
ri.kids[leafB.kind + ':' + leafB.id] = leafB;
|
|
3347
|
+
ri.raws[edge.to] = 1;
|
|
3348
|
+
var piId = 'p~in~' + bAid + '~' + edge.from;
|
|
3349
|
+
recB.stubs[piId + '=>' + IN(leafB.kind, leafB.id)] = { src: piId, tgt: IN(leafB.kind, leafB.id), stub: true };
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
return { deepByAnchor: deepByAnchor, direct: direct, directTopCount: directTopCount, ports: ports };
|
|
3354
|
+
}
|
|
3355
|
+
|
|
2978
3356
|
// Micro-layout for a container's direct children when Internals is on:
|
|
2979
3357
|
// layered mini columns + intra-container edges. Each external relation gets
|
|
2980
3358
|
// its own small PORT node INSIDE the container (one per external
|
|
2981
3359
|
// counterpart; incoming left, outgoing right). Children connect to ports
|
|
2982
3360
|
// with short edges that never leave the box \u2014 the real cross-boundary line
|
|
2983
3361
|
// is only revealed on hover, or pinned while the port is selected.
|
|
2984
|
-
function innerLayout(entry) {
|
|
3362
|
+
function innerLayout(entry, deepCtx) {
|
|
3363
|
+
// Deep-flagged subsystems take the recursive path (deepCtx exists only
|
|
3364
|
+
// when Internals is on and the view has deep entries \u2014 see buildElements).
|
|
3365
|
+
if (deepCtx && entry.kind === 'subsystem' && deepCtx.deepByAnchor[anchorNodeId(entry)]) {
|
|
3366
|
+
return deepLayout(entry, deepCtx.ports[anchorNodeId(entry)]);
|
|
3367
|
+
}
|
|
2985
3368
|
var kids = state.internals && entry.hasKids ? childrenOf({ kind: entry.kind, id: entry.id }) : [];
|
|
2986
3369
|
if (!kids.length) return null;
|
|
2987
3370
|
var scope = { kind: entry.kind, id: entry.id };
|
|
@@ -3553,12 +3936,42 @@ var MODEL = __MODEL_JSON__;
|
|
|
3553
3936
|
if (scope.kind === 'types' || scope.kind === 'databases') return buildTypeElements();
|
|
3554
3937
|
var entries = childrenOf(scope);
|
|
3555
3938
|
var eles = [];
|
|
3939
|
+
var deepCtx = buildDeepContext(scope, entries);
|
|
3556
3940
|
var ve = viewEdges(scope, entries);
|
|
3557
3941
|
// Data-coupling overlay: same scoping pipeline, a different edge source.
|
|
3558
3942
|
var vd = state.dataCoupling ? viewEdges(scope, entries, MODEL.dataEdges) : { agg: {}, ghosts: {} };
|
|
3559
3943
|
Object.keys(vd.ghosts).forEach(function (g) { if (!ve.ghosts[g]) ve.ghosts[g] = vd.ghosts[g]; });
|
|
3560
3944
|
var inners = {};
|
|
3561
|
-
entries.forEach(function (e) { inners[anchorNodeId(e)] = innerLayout(e); });
|
|
3945
|
+
entries.forEach(function (e) { inners[anchorNodeId(e)] = innerLayout(e, deepCtx); });
|
|
3946
|
+
|
|
3947
|
+
// Deep-mode recursive tile emission: a nested container becomes a cytoscape
|
|
3948
|
+
// compound parent (no explicit position \u2014 a compound derives its bounds
|
|
3949
|
+
// from its children); leaves and EMPTY containers are plain positioned
|
|
3950
|
+
// nodes. ox/oy = the emitting container's absolute top-left; tile.x/y are
|
|
3951
|
+
// centres relative to it, so offsets accumulate top-down. Search dimming
|
|
3952
|
+
// propagates the TOP entry's dim to the whole subtree.
|
|
3953
|
+
function emitDeepTiles(tiles, parentNodeId, ox, oy, dimCls) {
|
|
3954
|
+
tiles.forEach(function (tile) {
|
|
3955
|
+
var ax = ox + tile.x, ay = oy + tile.y;
|
|
3956
|
+
if (tile.sub) {
|
|
3957
|
+
var nid = SN(tile.kid.id);
|
|
3958
|
+
var selCls = state.selectedKind === 'subsystem' && state.selected === tile.kid.id ? ' sel' : '';
|
|
3959
|
+
var nested = {
|
|
3960
|
+
data: { id: nid, parent: parentNodeId, label: nameOf(tile.kid), w: tile.w, h: tile.h, tw: tile.w - 16 },
|
|
3961
|
+
classes: 'subsysBox' + (tile.sub.tiles.length ? ' drillable' : '') + dimCls + selCls,
|
|
3962
|
+
};
|
|
3963
|
+
if (!tile.sub.tiles.length) nested.position = { x: ax, y: ay };
|
|
3964
|
+
eles.push(nested);
|
|
3965
|
+
emitDeepTiles(tile.sub.tiles, nid, ax - tile.w / 2, ay - tile.h / 2, dimCls);
|
|
3966
|
+
} else {
|
|
3967
|
+
eles.push({
|
|
3968
|
+
data: { id: IN(tile.kid.kind, tile.kid.id), parent: parentNodeId, label: nameOf(tile.kid), w: INNER_W, h: INNER_H, tw: INNER_W - 10 },
|
|
3969
|
+
position: { x: ax, y: ay },
|
|
3970
|
+
classes: 'inner' + dimCls,
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3562
3975
|
|
|
3563
3976
|
// Resolve a port's reveal target(s) in THIS view. Preference order: the
|
|
3564
3977
|
// MATCHING PORT inside the counterpart's container (a port-to-port line
|
|
@@ -3690,17 +4103,25 @@ var MODEL = __MODEL_JSON__;
|
|
|
3690
4103
|
+ (state.showIssues && issuesBySpec[e.id] ? ' hasIssue' : '')
|
|
3691
4104
|
+ (state.selectedKind === e.kind && state.selected === e.id ? ' sel' : '');
|
|
3692
4105
|
if (inner) {
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
4106
|
+
var boxNode = { data: { id: aid, label: e.kind === 'subsystem' ? nameOf(e) : nameOf(e), w: p.w, h: p.h, tw: p.w - 16 }, classes: classes };
|
|
4107
|
+
// An EMPTY deep boundary box has no children, so it is NOT a compound
|
|
4108
|
+
// parent \u2014 it needs (and honours) an explicit position and size.
|
|
4109
|
+
if (inner.deep && !inner.tiles.length && !(inner.proxies && inner.proxies.length)) boxNode.position = { x: p.x, y: p.y };
|
|
4110
|
+
eles.push(boxNode);
|
|
4111
|
+
if (inner.deep) {
|
|
4112
|
+
emitDeepTiles(inner.tiles, aid, p.x - p.w / 2, p.y - p.h / 2, dim ? ' dimmed' : '');
|
|
4113
|
+
} else {
|
|
4114
|
+
inner.tiles.forEach(function (tile) {
|
|
4115
|
+
eles.push({
|
|
4116
|
+
data: {
|
|
4117
|
+
id: IN(tile.kid.kind, tile.kid.id), parent: aid,
|
|
4118
|
+
label: nameOf(tile.kid), w: INNER_W, h: INNER_H, tw: INNER_W - 10,
|
|
4119
|
+
},
|
|
4120
|
+
position: { x: p.x - p.w / 2 + tile.x, y: p.y - p.h / 2 + tile.y },
|
|
4121
|
+
classes: 'inner' + (dim ? ' dimmed' : ''),
|
|
4122
|
+
});
|
|
3702
4123
|
});
|
|
3703
|
-
}
|
|
4124
|
+
}
|
|
3704
4125
|
(inner.proxies || []).forEach(function (px) {
|
|
3705
4126
|
eles.push({
|
|
3706
4127
|
data: {
|
|
@@ -3853,9 +4274,28 @@ var MODEL = __MODEL_JSON__;
|
|
|
3853
4274
|
|
|
3854
4275
|
var dimmedAnchors = {};
|
|
3855
4276
|
entries.forEach(function (e) { if (state.query && !matches(e)) dimmedAnchors[anchorNodeId(e)] = true; });
|
|
4277
|
+
|
|
4278
|
+
// Deep mode: ONE direct line per related pair of concrete visible nodes \u2014
|
|
4279
|
+
// leaf tiles at any depth and/or top-level component boxes (deduped by
|
|
4280
|
+
// buildDeepContext). These lines cross nested boundaries on purpose.
|
|
4281
|
+
if (deepCtx) {
|
|
4282
|
+
var ddi = 0;
|
|
4283
|
+
Object.keys(deepCtx.direct).sort().forEach(function (key) {
|
|
4284
|
+
var d = deepCtx.direct[key];
|
|
4285
|
+
var ddim = state.query && (dimmedAnchors[d.aTop] || dimmedAnchors[d.bTop]);
|
|
4286
|
+
eles.push({
|
|
4287
|
+
data: { id: 'dd' + (ddi++), source: d.src, target: d.tgt, lbl: '' },
|
|
4288
|
+
classes: 'inneredge' + (d.cross ? ' cross' : '') + (ddim ? ' dimmed' : ''),
|
|
4289
|
+
});
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
|
|
3856
4293
|
var i = 0;
|
|
3857
4294
|
Object.keys(ve.agg).forEach(function (key) {
|
|
3858
4295
|
var e = ve.agg[key];
|
|
4296
|
+
// Prefer the leaf lines: drop an aggregated container edge whose
|
|
4297
|
+
// constituent relations were ALL drawn as direct deep lines above.
|
|
4298
|
+
if (deepCtx && deepCtx.directTopCount[key] >= e.n) return;
|
|
3859
4299
|
var bundle = e.n > 1;
|
|
3860
4300
|
var dim = state.query && (dimmedAnchors[e.src] || dimmedAnchors[e.tgt]);
|
|
3861
4301
|
var route = routeData(e.src, e.tgt, key);
|
|
@@ -4114,22 +4554,58 @@ var MODEL = __MODEL_JSON__;
|
|
|
4114
4554
|
}
|
|
4115
4555
|
return path;
|
|
4116
4556
|
}
|
|
4557
|
+
// Crumb compaction (compaction stage 5) is a RENDER MODE, not marker-based
|
|
4558
|
+
// reparenting: renderCrumbs rebuilds #crumbs' innerHTML on every navigation,
|
|
4559
|
+
// so nodes physically moved elsewhere would be destroyed by the next render.
|
|
4560
|
+
// The header-compaction stage toggles crumbsCompact and re-renders; compact
|
|
4561
|
+
// keeps the CURRENT scope visible and folds the ancestors into an ordered
|
|
4562
|
+
// "\\u2026" dropdown (document order, root first) whose entries navigate
|
|
4563
|
+
// exactly like the crumbs they replace.
|
|
4564
|
+
var crumbsCompact = false;
|
|
4565
|
+
var lastCrumbsHtml; // no initializer: the boot render at cy-init time precedes this line
|
|
4566
|
+
// Set by the header-compaction IIFE: crumb re-renders change the header's
|
|
4567
|
+
// CONTENT width without resizing #hdr itself (it is edge-anchored), so the
|
|
4568
|
+
// ResizeObserver never fires for them \u2014 renderCrumbs nudges a reflow here.
|
|
4569
|
+
var headerReflowHook = null;
|
|
4570
|
+
function crumbBtnHtml(p, cur) {
|
|
4571
|
+
return '<button class="crumb' + (cur ? ' cur' : '') + '" data-ck="' + p.kind + '" data-ci="' + (p.id || '') + '">' + p.label + '</button>';
|
|
4572
|
+
}
|
|
4117
4573
|
function renderCrumbs() {
|
|
4118
4574
|
var el = document.getElementById('crumbs');
|
|
4119
4575
|
var path = crumbPath();
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
+
|
|
4124
|
-
|
|
4576
|
+
var html;
|
|
4577
|
+
if (crumbsCompact && path.length > 1) {
|
|
4578
|
+
html = '<span class="dropdown" id="crumbDd">'
|
|
4579
|
+
+ '<button class="crumb crumbmore" id="crumbMoreBtn" title="Show the collapsed ancestor path">\\u2026</button>'
|
|
4580
|
+
+ '<span class="menu" id="crumbMenu">'
|
|
4581
|
+
+ path.slice(0, path.length - 1).map(function (p) {
|
|
4582
|
+
return '<button data-ck="' + p.kind + '" data-ci="' + (p.id || '') + '">' + p.label + '</button>';
|
|
4583
|
+
}).join('')
|
|
4584
|
+
+ '</span></span>'
|
|
4585
|
+
+ '<span class="sep">\\u203A</span>'
|
|
4586
|
+
+ crumbBtnHtml(path[path.length - 1], true);
|
|
4587
|
+
} else {
|
|
4588
|
+
html = path.map(function (p, i) {
|
|
4589
|
+
var cur = i === path.length - 1;
|
|
4590
|
+
return crumbBtnHtml(p, cur) + (cur ? '' : '<span class="sep">\\u203A</span>');
|
|
4591
|
+
}).join('');
|
|
4592
|
+
}
|
|
4593
|
+
// No-op renders keep the already-wired nodes (and an open "\\u2026" menu)
|
|
4594
|
+
// intact \u2014 and don't churn the reflow scheduler while a search query types.
|
|
4595
|
+
if (html === lastCrumbsHtml) return;
|
|
4596
|
+
lastCrumbsHtml = html;
|
|
4597
|
+
el.innerHTML = html;
|
|
4125
4598
|
var btns = el.querySelectorAll('button');
|
|
4126
4599
|
for (var i = 0; i < btns.length; i++) {
|
|
4127
4600
|
(function (b) {
|
|
4601
|
+
if (!b.getAttribute('data-ck')) return; // the "\\u2026" trigger toggles, never navigates
|
|
4128
4602
|
b.addEventListener('click', function () {
|
|
4129
4603
|
navigateTo(b.getAttribute('data-ck'), b.getAttribute('data-ci') || null);
|
|
4130
4604
|
});
|
|
4131
4605
|
})(btns[i]);
|
|
4132
4606
|
}
|
|
4607
|
+
if (crumbsCompact && path.length > 1) wireDropdown('crumbDd', 'crumbMoreBtn');
|
|
4608
|
+
if (headerReflowHook) headerReflowHook();
|
|
4133
4609
|
}
|
|
4134
4610
|
function renderViewHint() {
|
|
4135
4611
|
if (state.view.kind === 'types' || state.view.kind === 'databases') {
|
|
@@ -4396,7 +4872,16 @@ var MODEL = __MODEL_JSON__;
|
|
|
4396
4872
|
return c ? { kind: 'subsystem', id: c.subsystem } : { kind: 'system', id: null };
|
|
4397
4873
|
}
|
|
4398
4874
|
|
|
4399
|
-
|
|
4875
|
+
// While the search input is compacted behind the magnifier icon (compaction
|
|
4876
|
+
// stage 3), a non-empty query must stay discoverable \u2014 mark the icon with an
|
|
4877
|
+
// accent dot. The class is kept in sync on every query edit; the dot is only
|
|
4878
|
+
// ever visible while the compact icon itself is.
|
|
4879
|
+
function updateSearchBadge() {
|
|
4880
|
+
var b = document.getElementById('searchBtn');
|
|
4881
|
+
if (b && b.classList) b.classList[state.query ? 'add' : 'remove']('hasq');
|
|
4882
|
+
}
|
|
4883
|
+
document.getElementById('search').addEventListener('input', function (ev) { state.query = ev.target.value.trim(); updateSearchBadge(); rebuild(false); });
|
|
4884
|
+
updateSearchBadge();
|
|
4400
4885
|
// Sync each View toggle's checkbox from the (possibly persisted) state, then
|
|
4401
4886
|
// persist on change so the choices survive a refresh (see persist()/saved).
|
|
4402
4887
|
document.getElementById('internalsToggle').checked = state.internals;
|
|
@@ -4450,9 +4935,24 @@ var MODEL = __MODEL_JSON__;
|
|
|
4450
4935
|
})(btns[i]);
|
|
4451
4936
|
}
|
|
4452
4937
|
})();
|
|
4938
|
+
// Compaction stage 4 replaces the mode tabs with one dropdown trigger; its
|
|
4939
|
+
// label must follow the CURRENT mode. updateHeaderSegs runs on every rebuild,
|
|
4940
|
+
// so a mode change made while compact re-labels the trigger immediately.
|
|
4941
|
+
function updateModeBtn() {
|
|
4942
|
+
var b = document.getElementById('modeBtn');
|
|
4943
|
+
if (!b) return;
|
|
4944
|
+
var lbl = state.view.kind === 'types' ? 'Types' : state.view.kind === 'databases' ? 'Databases' : 'Components';
|
|
4945
|
+
b.textContent = lbl + ' \\u25BE';
|
|
4946
|
+
}
|
|
4453
4947
|
function updateHeaderSegs() {
|
|
4454
4948
|
var seg = document.getElementById('modeSeg');
|
|
4455
4949
|
var btns = seg.querySelectorAll('button');
|
|
4950
|
+
if (!btns.length) {
|
|
4951
|
+
// Compaction stage 4 moved the real tab buttons into the mode dropdown \u2014
|
|
4952
|
+
// keep driving THEIR active classes there (they move back node-identical).
|
|
4953
|
+
var mm = document.getElementById('modeMenu');
|
|
4954
|
+
if (mm && mm.querySelectorAll) btns = mm.querySelectorAll('button');
|
|
4955
|
+
}
|
|
4456
4956
|
for (var i = 0; i < btns.length; i++) {
|
|
4457
4957
|
var vm = btns[i].getAttribute('data-vm');
|
|
4458
4958
|
var active = vm === 'components'
|
|
@@ -4460,6 +4960,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
4460
4960
|
: vm === state.view.kind;
|
|
4461
4961
|
if (btns[i].classList) btns[i].classList[active ? 'add' : 'remove']('active');
|
|
4462
4962
|
}
|
|
4963
|
+
updateModeBtn();
|
|
4463
4964
|
var td = document.getElementById('typesDetailSeg');
|
|
4464
4965
|
td.style.display = (state.view.kind === 'types' || state.view.kind === 'databases') ? '' : 'none';
|
|
4465
4966
|
var tbs = td.querySelectorAll('button');
|
|
@@ -4523,7 +5024,13 @@ var MODEL = __MODEL_JSON__;
|
|
|
4523
5024
|
var r = btn.getBoundingClientRect();
|
|
4524
5025
|
menu.style.top = (r.bottom + 6) + 'px';
|
|
4525
5026
|
menu.style.left = 'auto';
|
|
4526
|
-
|
|
5027
|
+
// Right-aligned to the trigger, but never pushed off the LEFT edge \u2014 the
|
|
5028
|
+
// compact search / crumb triggers (header compaction) sit on the header's
|
|
5029
|
+
// left side, where a 200px menu right-aligned to a narrow button would clip.
|
|
5030
|
+
var right = Math.max(6, window.innerWidth - r.right);
|
|
5031
|
+
var mw = menu.getBoundingClientRect ? menu.getBoundingClientRect().width : 0;
|
|
5032
|
+
if (mw && window.innerWidth - right - mw < 6) right = Math.max(6, window.innerWidth - mw - 6);
|
|
5033
|
+
menu.style.right = right + 'px';
|
|
4527
5034
|
}
|
|
4528
5035
|
function wireDropdown(ddId, btnId) {
|
|
4529
5036
|
var dd = document.getElementById(ddId);
|
|
@@ -4542,11 +5049,29 @@ var MODEL = __MODEL_JSON__;
|
|
|
4542
5049
|
var ldd = wireDropdown('layoutDd', 'layoutBtn');
|
|
4543
5050
|
var sdd = wireDropdown('settingsDd', 'settingsBtn');
|
|
4544
5051
|
var mdd = wireDropdown('moreDd', 'moreBtn');
|
|
5052
|
+
// Compact stand-ins (header compaction stages 3-4): the search panel and the
|
|
5053
|
+
// mode-tab dropdown are ordinary dropdowns; their triggers stay hidden until
|
|
5054
|
+
// their compaction stage shows them, so wiring them here is inert at full width.
|
|
5055
|
+
var qdd = wireDropdown('searchDd', 'searchBtn');
|
|
5056
|
+
var vdd = wireDropdown('modeDd', 'modeBtn');
|
|
5057
|
+
// Opening the compact search panel focuses the REAL input (stage 3 moves the
|
|
5058
|
+
// node, never clones it, so its input listener keeps driving state.query).
|
|
5059
|
+
// Registered after wireDropdown's toggle, so 'open' reflects the new state.
|
|
5060
|
+
document.getElementById('searchBtn').addEventListener('click', function () {
|
|
5061
|
+
if (String(qdd.className || '').indexOf('open') >= 0) {
|
|
5062
|
+
var inp = document.getElementById('search');
|
|
5063
|
+
if (inp && inp.focus) inp.focus();
|
|
5064
|
+
}
|
|
5065
|
+
});
|
|
4545
5066
|
// Keep the settings panel open while flipping switches (clicks inside it don't
|
|
4546
5067
|
// bubble to the document-level close handler).
|
|
4547
5068
|
(function () {
|
|
4548
5069
|
var m = document.getElementById('settingsMenu');
|
|
4549
5070
|
if (m && m.addEventListener) m.addEventListener('click', function (ev) { if (ev && ev.stopPropagation) ev.stopPropagation(); });
|
|
5071
|
+
// Same for the floating search panel: clicking into the input must not
|
|
5072
|
+
// bubble to the document-level close handler and shut the panel mid-typing.
|
|
5073
|
+
var sm = document.getElementById('searchMenu');
|
|
5074
|
+
if (sm && sm.addEventListener) sm.addEventListener('click', function (ev) { if (ev && ev.stopPropagation) ev.stopPropagation(); });
|
|
4550
5075
|
})();
|
|
4551
5076
|
|
|
4552
5077
|
// Layout picker: choose the auto-layout algorithm. Components use cytoscape's
|
|
@@ -4570,6 +5095,12 @@ var MODEL = __MODEL_JSON__;
|
|
|
4570
5095
|
});
|
|
4571
5096
|
updateLayoutBtn();
|
|
4572
5097
|
|
|
5098
|
+
// The compact crumb dropdown (compaction stage 5) is re-created by every
|
|
5099
|
+
// compact crumb render, so it is looked up per close instead of captured.
|
|
5100
|
+
function closeCrumbDd() {
|
|
5101
|
+
var cdd = document.getElementById('crumbDd');
|
|
5102
|
+
if (cdd && cdd.classList) cdd.classList.remove('open');
|
|
5103
|
+
}
|
|
4573
5104
|
if (document.addEventListener) {
|
|
4574
5105
|
document.addEventListener('click', function () {
|
|
4575
5106
|
if (dd.classList) dd.classList.remove('open');
|
|
@@ -4577,6 +5108,9 @@ var MODEL = __MODEL_JSON__;
|
|
|
4577
5108
|
if (ldd.classList) ldd.classList.remove('open');
|
|
4578
5109
|
if (sdd.classList) sdd.classList.remove('open');
|
|
4579
5110
|
if (mdd && mdd.classList) mdd.classList.remove('open');
|
|
5111
|
+
if (qdd && qdd.classList) qdd.classList.remove('open');
|
|
5112
|
+
if (vdd && vdd.classList) vdd.classList.remove('open');
|
|
5113
|
+
closeCrumbDd();
|
|
4580
5114
|
});
|
|
4581
5115
|
document.addEventListener('keydown', function (ev) {
|
|
4582
5116
|
if (ev.key === 'Escape') {
|
|
@@ -4584,16 +5118,27 @@ var MODEL = __MODEL_JSON__;
|
|
|
4584
5118
|
if (modal.classList && String(modal.className).indexOf('open') >= 0) { closeFlow(); return; }
|
|
4585
5119
|
setPresentation(false);
|
|
4586
5120
|
if (dd.classList) dd.classList.remove('open');
|
|
5121
|
+
if (qdd && qdd.classList) qdd.classList.remove('open');
|
|
5122
|
+
if (vdd && vdd.classList) vdd.classList.remove('open');
|
|
5123
|
+
closeCrumbDd();
|
|
4587
5124
|
}
|
|
4588
5125
|
});
|
|
4589
5126
|
}
|
|
4590
5127
|
|
|
4591
|
-
// \u2500\u2500 Responsive header
|
|
4592
|
-
// When the floating header no longer fits its controls,
|
|
4593
|
-
//
|
|
4594
|
-
//
|
|
4595
|
-
//
|
|
4596
|
-
//
|
|
5128
|
+
// \u2500\u2500 Responsive header compaction \u2192 ordered stages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5129
|
+
// When the floating header no longer fits its controls, standard reusable
|
|
5130
|
+
// COMPACTION BEHAVIORS apply progressively \u2014 each stage only while the row
|
|
5131
|
+
// still overflows \u2014 and restore in REVERSE order when space returns:
|
|
5132
|
+
// 1. trailing buttons fold into the "\u22EF" menu (least-used first, one by one)
|
|
5133
|
+
// 2. the View dropdown folds in after them
|
|
5134
|
+
// 3. the search input compacts to a \u{1F50D} icon + floating panel
|
|
5135
|
+
// 4. the mode tabs compact to one current-mode dropdown
|
|
5136
|
+
// 5. ancestor crumbs compact into an ordered "\u2026" dropdown
|
|
5137
|
+
// Stages 1-2 move whole items (listeners survive reparenting) with a hidden
|
|
5138
|
+
// placeholder pinning each item's original spot for restore; stage 5 is a
|
|
5139
|
+
// render mode (renderCrumbs rebuilds its innerHTML, so reparenting would not
|
|
5140
|
+
// survive navigation). Nothing here is persisted \u2014 compaction is purely
|
|
5141
|
+
// responsive to the available width.
|
|
4597
5142
|
(function () {
|
|
4598
5143
|
if (typeof window === 'undefined') return;
|
|
4599
5144
|
var hdr = document.getElementById('hdr');
|
|
@@ -4621,7 +5166,122 @@ var MODEL = __MODEL_JSON__;
|
|
|
4621
5166
|
}
|
|
4622
5167
|
return markers[id];
|
|
4623
5168
|
}
|
|
4624
|
-
|
|
5169
|
+
// Fold stage (the classic behavior): move items into the "\u22EF" menu ONE per
|
|
5170
|
+
// apply() call \u2014 the reflow loop keeps a stage active until it reports no
|
|
5171
|
+
// further progress, preserving the original per-button granularity.
|
|
5172
|
+
function foldStage(ids) {
|
|
5173
|
+
var folded = [];
|
|
5174
|
+
return {
|
|
5175
|
+
apply: function () {
|
|
5176
|
+
while (folded.length < ids.length) {
|
|
5177
|
+
var id = ids[folded.length];
|
|
5178
|
+
var el = movableFor(id);
|
|
5179
|
+
if (!el || el === moreDd || el.parentNode === moreMenu) { folded.push({ el: null, marker: null }); continue; }
|
|
5180
|
+
var m = markerFor(id, el);
|
|
5181
|
+
// A dropdown moved while open would strand its fixed-positioned menu.
|
|
5182
|
+
if (el.classList) el.classList.remove('open');
|
|
5183
|
+
moreDd.style.display = '';
|
|
5184
|
+
moreMenu.appendChild(el);
|
|
5185
|
+
folded.push({ el: el, marker: m });
|
|
5186
|
+
return true;
|
|
5187
|
+
}
|
|
5188
|
+
return false;
|
|
5189
|
+
},
|
|
5190
|
+
restore: function () {
|
|
5191
|
+
for (var i = folded.length - 1; i >= 0; i--) {
|
|
5192
|
+
var it = folded[i];
|
|
5193
|
+
if (it.el && it.marker && it.marker.parentNode) it.marker.parentNode.insertBefore(it.el, it.marker);
|
|
5194
|
+
}
|
|
5195
|
+
folded = [];
|
|
5196
|
+
},
|
|
5197
|
+
};
|
|
5198
|
+
}
|
|
5199
|
+
// Stage 3: the search input compacts behind a \u{1F50D} icon; the REAL input node
|
|
5200
|
+
// MOVES into the floating panel (fixed-positioned by the same helper as
|
|
5201
|
+
// every dropdown menu), so its input listener keeps driving state.query.
|
|
5202
|
+
function searchStage() {
|
|
5203
|
+
var on = false;
|
|
5204
|
+
return {
|
|
5205
|
+
apply: function () {
|
|
5206
|
+
if (on) return false;
|
|
5207
|
+
var inp = document.getElementById('search');
|
|
5208
|
+
var ddw = document.getElementById('searchDd');
|
|
5209
|
+
var menu = document.getElementById('searchMenu');
|
|
5210
|
+
if (!inp || !ddw || !menu) return false;
|
|
5211
|
+
menu.appendChild(inp);
|
|
5212
|
+
ddw.style.display = '';
|
|
5213
|
+
on = true;
|
|
5214
|
+
return true;
|
|
5215
|
+
},
|
|
5216
|
+
restore: function () {
|
|
5217
|
+
if (!on) return;
|
|
5218
|
+
on = false;
|
|
5219
|
+
var inp = document.getElementById('search');
|
|
5220
|
+
var ddw = document.getElementById('searchDd');
|
|
5221
|
+
if (inp && ddw && ddw.parentNode) ddw.parentNode.insertBefore(inp, ddw);
|
|
5222
|
+
if (ddw) { ddw.style.display = 'none'; if (ddw.classList) ddw.classList.remove('open'); }
|
|
5223
|
+
},
|
|
5224
|
+
};
|
|
5225
|
+
}
|
|
5226
|
+
// Stage 4: the mode tabs collapse into ONE dropdown labelled with the
|
|
5227
|
+
// current mode. The REAL tab buttons move into its menu (listeners and
|
|
5228
|
+
// active styling survive); updateHeaderSegs keeps the trigger label in
|
|
5229
|
+
// sync when the mode changes while compact.
|
|
5230
|
+
function modeStage() {
|
|
5231
|
+
var moved = [];
|
|
5232
|
+
return {
|
|
5233
|
+
apply: function () {
|
|
5234
|
+
if (moved.length) return false;
|
|
5235
|
+
var seg = document.getElementById('modeSeg');
|
|
5236
|
+
var ddw = document.getElementById('modeDd');
|
|
5237
|
+
var menu = document.getElementById('modeMenu');
|
|
5238
|
+
if (!seg || !ddw || !menu) return false;
|
|
5239
|
+
var btns = seg.querySelectorAll('button');
|
|
5240
|
+
if (!btns.length) return false;
|
|
5241
|
+
for (var i = 0; i < btns.length; i++) moved.push(btns[i]);
|
|
5242
|
+
for (var j = 0; j < moved.length; j++) menu.appendChild(moved[j]);
|
|
5243
|
+
seg.style.display = 'none';
|
|
5244
|
+
ddw.style.display = '';
|
|
5245
|
+
updateModeBtn();
|
|
5246
|
+
return true;
|
|
5247
|
+
},
|
|
5248
|
+
restore: function () {
|
|
5249
|
+
if (!moved.length) return;
|
|
5250
|
+
var seg = document.getElementById('modeSeg');
|
|
5251
|
+
var ddw = document.getElementById('modeDd');
|
|
5252
|
+
for (var i = 0; i < moved.length; i++) seg.appendChild(moved[i]);
|
|
5253
|
+
moved = [];
|
|
5254
|
+
seg.style.display = '';
|
|
5255
|
+
if (ddw) { ddw.style.display = 'none'; if (ddw.classList) ddw.classList.remove('open'); }
|
|
5256
|
+
},
|
|
5257
|
+
};
|
|
5258
|
+
}
|
|
5259
|
+
// Stage 5: crumb compaction is a render-mode toggle consulted by
|
|
5260
|
+
// renderCrumbs itself \u2014 see crumbsCompact there. Never reparenting.
|
|
5261
|
+
function crumbStage() {
|
|
5262
|
+
return {
|
|
5263
|
+
apply: function () {
|
|
5264
|
+
if (crumbsCompact) return false;
|
|
5265
|
+
crumbsCompact = true;
|
|
5266
|
+
renderCrumbs();
|
|
5267
|
+
return true;
|
|
5268
|
+
},
|
|
5269
|
+
restore: function () {
|
|
5270
|
+
if (!crumbsCompact) return;
|
|
5271
|
+
crumbsCompact = false;
|
|
5272
|
+
renderCrumbs();
|
|
5273
|
+
},
|
|
5274
|
+
};
|
|
5275
|
+
}
|
|
5276
|
+
// Ordered compaction stages: applied first-to-last only while the header
|
|
5277
|
+
// overflows, restored last-to-first when space returns.
|
|
5278
|
+
var STAGES = [
|
|
5279
|
+
foldStage(COLLAPSE), // 1: trailing buttons \u2192 "\u22EF" menu
|
|
5280
|
+
foldStage(['settingsBtn']), // 2: the View dropdown folds in too
|
|
5281
|
+
searchStage(), // 3: search input \u2192 \u{1F50D} + floating panel
|
|
5282
|
+
modeStage(), // 4: mode tabs \u2192 current-mode dropdown
|
|
5283
|
+
crumbStage(), // 5: ancestor crumbs \u2192 "\u2026" dropdown
|
|
5284
|
+
];
|
|
4625
5285
|
// Signed fit measure in px: positive = overflowing, negative = headroom.
|
|
4626
5286
|
// The header is a flex row whose ONLY flex:1 child is the .spacer, so the
|
|
4627
5287
|
// spacer's rendered width IS the free space -- it grows to absorb all slack
|
|
@@ -4637,33 +5297,54 @@ var MODEL = __MODEL_JSON__;
|
|
|
4637
5297
|
var slack = spacer ? spacer.getBoundingClientRect().width : 0;
|
|
4638
5298
|
return (hdr.scrollWidth - hdr.clientWidth) - slack;
|
|
4639
5299
|
}
|
|
5300
|
+
var inReflow = false;
|
|
4640
5301
|
function reflow() {
|
|
4641
5302
|
// Not laid out (hidden tab, non-browser DOM) \u2014 measuring would misfire.
|
|
4642
5303
|
var box = hdr.getBoundingClientRect();
|
|
4643
5304
|
if (!box || box.width <= 0) return;
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
var
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
5305
|
+
inReflow = true;
|
|
5306
|
+
try {
|
|
5307
|
+
// The floating search panel must survive a reflow cycle: restore-all
|
|
5308
|
+
// would close it (and reparenting blurs the input), so capture its
|
|
5309
|
+
// open/focus state up front and reinstate it after the stage walk.
|
|
5310
|
+
var ddw = document.getElementById('searchDd');
|
|
5311
|
+
var inp = document.getElementById('search');
|
|
5312
|
+
var searchOpen = !!(ddw && String(ddw.className || '').indexOf('open') >= 0);
|
|
5313
|
+
var searchFocus = false;
|
|
5314
|
+
try {
|
|
5315
|
+
var ae = (typeof ROOT !== 'undefined' && ROOT ? ROOT : document).activeElement;
|
|
5316
|
+
searchFocus = !!(ae && inp && ae === inp);
|
|
5317
|
+
} catch (e) { /* stubbed DOM */ }
|
|
5318
|
+
// Restore every stage in REVERSE order, then re-apply progressively
|
|
5319
|
+
// while the row still overflows (idempotent).
|
|
5320
|
+
for (var i = STAGES.length - 1; i >= 0; i--) STAGES[i].restore();
|
|
5321
|
+
moreDd.style.display = 'none';
|
|
5322
|
+
hdr.scrollLeft = 0;
|
|
5323
|
+
// Demand a few px of headroom, not a bare fit \u2014 the marginal-fit widths
|
|
5324
|
+
// are exactly where the phantom scrollbar appeared. Every apply()
|
|
5325
|
+
// changes the very widths being measured, but restore-all + a strictly
|
|
5326
|
+
// forward stage walk make the outcome a pure function of the current
|
|
5327
|
+
// width, and reflow never reschedules itself (renderCrumbs' nudge is
|
|
5328
|
+
// suppressed via inReflow, and #hdr's own box never changes here), so
|
|
5329
|
+
// boundary widths settle in ONE pass instead of oscillating.
|
|
5330
|
+
var si = 0;
|
|
5331
|
+
var guard = 0;
|
|
5332
|
+
var bound = COLLAPSE.length + STAGES.length + 8;
|
|
5333
|
+
while (overflowPx() > -8 && si < STAGES.length && guard < bound) {
|
|
5334
|
+
guard++;
|
|
5335
|
+
if (!STAGES[si].apply()) si++;
|
|
5336
|
+
}
|
|
5337
|
+
if (searchOpen || searchFocus) {
|
|
5338
|
+
var compactNow = ddw && ddw.style && ddw.style.display !== 'none';
|
|
5339
|
+
if (compactNow && searchOpen) {
|
|
5340
|
+
if (ddw.classList) ddw.classList.add('open');
|
|
5341
|
+
positionDropdownMenu(ddw, document.getElementById('searchBtn'));
|
|
5342
|
+
}
|
|
5343
|
+
if (searchFocus && inp && inp.focus) inp.focus();
|
|
5344
|
+
}
|
|
5345
|
+
} finally {
|
|
5346
|
+
inReflow = false;
|
|
4665
5347
|
}
|
|
4666
|
-
if (collapsed.length === 0) moreDd.style.display = 'none';
|
|
4667
5348
|
}
|
|
4668
5349
|
var raf = null;
|
|
4669
5350
|
var defer = window.requestAnimationFrame
|
|
@@ -4673,6 +5354,9 @@ var MODEL = __MODEL_JSON__;
|
|
|
4673
5354
|
if (raf !== null) return;
|
|
4674
5355
|
raf = defer(function () { raf = null; reflow(); });
|
|
4675
5356
|
}
|
|
5357
|
+
// Crumb re-renders change the header's content width without resizing #hdr
|
|
5358
|
+
// itself \u2014 renderCrumbs nudges a reflow through this hook (no-op mid-reflow).
|
|
5359
|
+
headerReflowHook = function () { if (!inReflow) schedule(); };
|
|
4676
5360
|
if (typeof ResizeObserver !== 'undefined') {
|
|
4677
5361
|
new ResizeObserver(schedule).observe(hdr);
|
|
4678
5362
|
} else if (window.addEventListener) {
|
|
@@ -6003,7 +6687,7 @@ var init_extensions = __esm({
|
|
|
6003
6687
|
});
|
|
6004
6688
|
|
|
6005
6689
|
// src/core/rules/types.ts
|
|
6006
|
-
var BUILTIN_PROFILES;
|
|
6690
|
+
var BUILTIN_PROFILES, PROJECT_KINDS;
|
|
6007
6691
|
var init_types = __esm({
|
|
6008
6692
|
"src/core/rules/types.ts"() {
|
|
6009
6693
|
"use strict";
|
|
@@ -6016,6 +6700,7 @@ var init_types = __esm({
|
|
|
6016
6700
|
"realtime-embedded",
|
|
6017
6701
|
"plc-cyclic"
|
|
6018
6702
|
];
|
|
6703
|
+
PROJECT_KINDS = ["fullstack", "system-of-systems", "monorepo"];
|
|
6019
6704
|
}
|
|
6020
6705
|
});
|
|
6021
6706
|
|
|
@@ -6196,44 +6881,13 @@ var init_type_references = __esm({
|
|
|
6196
6881
|
}
|
|
6197
6882
|
});
|
|
6198
6883
|
|
|
6199
|
-
// src/
|
|
6200
|
-
function
|
|
6201
|
-
|
|
6202
|
-
system: loadSystemSpec(),
|
|
6203
|
-
subsystems: loadSubsystemSpecs(),
|
|
6204
|
-
components: loadComponentSpecs(),
|
|
6205
|
-
interfaces: loadInterfaceSpecs(),
|
|
6206
|
-
implementations: loadImplementationSpecs(),
|
|
6207
|
-
types: loadTypeSpecs()
|
|
6208
|
-
};
|
|
6209
|
-
const digest = crypto.createHash("sha256").update(canonicalize(tree)).digest("hex");
|
|
6210
|
-
return { algorithm: "sha256", digest };
|
|
6211
|
-
}
|
|
6212
|
-
function stateIdEquals(a, b) {
|
|
6213
|
-
return !!a && !!b && a.algorithm === b.algorithm && a.digest === b.digest;
|
|
6214
|
-
}
|
|
6215
|
-
function canonicalize(value) {
|
|
6216
|
-
return JSON.stringify(sortKeys(value));
|
|
6217
|
-
}
|
|
6218
|
-
function sortKeys(v) {
|
|
6219
|
-
if (Array.isArray(v)) return v.map(sortKeys);
|
|
6220
|
-
if (v && typeof v === "object") {
|
|
6221
|
-
const src = v;
|
|
6222
|
-
const out = {};
|
|
6223
|
-
for (const k of Object.keys(src).sort()) {
|
|
6224
|
-
if (k === "createdAt" || k === "updatedAt") continue;
|
|
6225
|
-
out[k] = sortKeys(src[k]);
|
|
6226
|
-
}
|
|
6227
|
-
return out;
|
|
6228
|
-
}
|
|
6229
|
-
return v;
|
|
6884
|
+
// src/utils/filenames.ts
|
|
6885
|
+
function safeFilenamePart(value) {
|
|
6886
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
6230
6887
|
}
|
|
6231
|
-
var
|
|
6232
|
-
|
|
6233
|
-
"src/core/statehash.ts"() {
|
|
6888
|
+
var init_filenames = __esm({
|
|
6889
|
+
"src/utils/filenames.ts"() {
|
|
6234
6890
|
"use strict";
|
|
6235
|
-
crypto = __toESM(require("crypto"));
|
|
6236
|
-
init_specs2();
|
|
6237
6891
|
}
|
|
6238
6892
|
});
|
|
6239
6893
|
|
|
@@ -6694,6 +7348,66 @@ function projectOwnSurface(maxAudience) {
|
|
|
6694
7348
|
function projectChildSurface() {
|
|
6695
7349
|
return projectOwnSurface("project");
|
|
6696
7350
|
}
|
|
7351
|
+
function localName(id) {
|
|
7352
|
+
return id.split("::").pop();
|
|
7353
|
+
}
|
|
7354
|
+
function projectSubsystemSurface(subsystemId) {
|
|
7355
|
+
const system = loadSystemSpec();
|
|
7356
|
+
if (!system) {
|
|
7357
|
+
throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
|
|
7358
|
+
}
|
|
7359
|
+
const subsystems = loadSubsystemSpecs();
|
|
7360
|
+
const target = subsystems.find((s) => s.id === subsystemId);
|
|
7361
|
+
if (!target) {
|
|
7362
|
+
throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
|
|
7363
|
+
}
|
|
7364
|
+
const components = loadComponentSpecs();
|
|
7365
|
+
const interfaces = loadInterfaceSpecs();
|
|
7366
|
+
const types = loadTypeSpecs();
|
|
7367
|
+
const entries = [];
|
|
7368
|
+
const unprojectable = [];
|
|
7369
|
+
for (const pub of target.publicInterfaces ?? []) {
|
|
7370
|
+
if (!pub.component) continue;
|
|
7371
|
+
const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
|
|
7372
|
+
if (!comp) continue;
|
|
7373
|
+
if (!CROSS_BOUNDARY_TARGETS.has(comp.componentType)) {
|
|
7374
|
+
unprojectable.push({ component: pub.component, componentType: comp.componentType });
|
|
7375
|
+
continue;
|
|
7376
|
+
}
|
|
7377
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
|
|
7378
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
7379
|
+
entries.push({
|
|
7380
|
+
id: localName(pub.interface ?? comp.id),
|
|
7381
|
+
name: comp.name,
|
|
7382
|
+
// Family ceiling: a sibling surface is consumable by the system family only.
|
|
7383
|
+
audience: "project",
|
|
7384
|
+
type: pub.type ?? "Custom",
|
|
7385
|
+
// The snapshot carries the LOCAL portal name — consumers resolve cross-tree
|
|
7386
|
+
// refs by their final segment.
|
|
7387
|
+
component: localName(comp.id),
|
|
7388
|
+
methods,
|
|
7389
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
7390
|
+
// Project the backing component's auth + basePath so the codec can emit
|
|
7391
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
7392
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
7393
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
7394
|
+
details: pub.details ?? ""
|
|
7395
|
+
});
|
|
7396
|
+
}
|
|
7397
|
+
for (const skipped of unprojectable) {
|
|
7398
|
+
console.error(
|
|
7399
|
+
`[surfaces] skipped "${subsystemId}::${skipped.component}": a published ${skipped.componentType} can never serve a cross-boundary caller, so it stays out of every chained child's sibling surface \u2014 publish this surface through a Portal, a Gateway, or an Observer (for events).`
|
|
7400
|
+
);
|
|
7401
|
+
}
|
|
7402
|
+
return SurfaceSnapshotSchema.parse({
|
|
7403
|
+
projectName: `${system.name}::${subsystemId}`,
|
|
7404
|
+
origin: "generated",
|
|
7405
|
+
stateId: stateIdString(),
|
|
7406
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7407
|
+
interfaces: entries,
|
|
7408
|
+
types: computeTypeClosure(entries, types)
|
|
7409
|
+
});
|
|
7410
|
+
}
|
|
6697
7411
|
function listSnapshots(rootDir = getProjectRoot()) {
|
|
6698
7412
|
const dir = surfacesDir(rootDir);
|
|
6699
7413
|
if (!fs4.existsSync(dir)) return [];
|
|
@@ -6710,45 +7424,88 @@ function listSnapshots(rootDir = getProjectRoot()) {
|
|
|
6710
7424
|
function getSnapshot(projectName, rootDir = getProjectRoot()) {
|
|
6711
7425
|
return listSnapshots(rootDir).find((s) => s.projectName === projectName) ?? null;
|
|
6712
7426
|
}
|
|
7427
|
+
function snapshotFilename(projectName) {
|
|
7428
|
+
return `${safeFilenamePart(projectName)}.yaml`;
|
|
7429
|
+
}
|
|
6713
7430
|
function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
6714
7431
|
const dir = surfacesDir(rootDir);
|
|
6715
7432
|
fs4.mkdirSync(dir, { recursive: true });
|
|
6716
|
-
const p = path5.join(dir,
|
|
7433
|
+
const p = path5.join(dir, snapshotFilename(snapshot.projectName));
|
|
6717
7434
|
writeYamlFile(p, SurfaceSnapshotSchema.parse(snapshot));
|
|
6718
7435
|
return p;
|
|
6719
7436
|
}
|
|
6720
7437
|
function removeSnapshot(projectName, rootDir = getProjectRoot()) {
|
|
6721
|
-
const
|
|
6722
|
-
|
|
6723
|
-
fs4.
|
|
6724
|
-
|
|
7438
|
+
const dir = surfacesDir(rootDir);
|
|
7439
|
+
const direct = path5.join(dir, snapshotFilename(projectName));
|
|
7440
|
+
if (fs4.existsSync(direct)) {
|
|
7441
|
+
fs4.unlinkSync(direct);
|
|
7442
|
+
return true;
|
|
7443
|
+
}
|
|
7444
|
+
if (!fs4.existsSync(dir)) return false;
|
|
7445
|
+
for (const file of fs4.readdirSync(dir)) {
|
|
7446
|
+
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
|
|
7447
|
+
const p = path5.join(dir, file);
|
|
7448
|
+
try {
|
|
7449
|
+
const snap = SurfaceSnapshotSchema.parse(readYamlFile(p));
|
|
7450
|
+
if (snap.projectName === projectName) {
|
|
7451
|
+
fs4.unlinkSync(p);
|
|
7452
|
+
return true;
|
|
7453
|
+
}
|
|
7454
|
+
} catch {
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
return false;
|
|
6725
7458
|
}
|
|
6726
7459
|
function loadSurfaceSnapshots() {
|
|
6727
7460
|
return listSnapshots();
|
|
6728
7461
|
}
|
|
6729
|
-
function
|
|
6730
|
-
const
|
|
6731
|
-
|
|
7462
|
+
function selectPortalSpec(renderedSet, portalId) {
|
|
7463
|
+
const hit = renderedSet.find((spec) => spec.portalId === portalId);
|
|
7464
|
+
if (!hit) {
|
|
7465
|
+
const known = renderedSet.map((s) => s.portalId).join(", ");
|
|
7466
|
+
throw new Error(`Unknown portal "${portalId}" \u2014 this surface renders: ${known || "(no portals)"}.`);
|
|
7467
|
+
}
|
|
7468
|
+
return [hit];
|
|
7469
|
+
}
|
|
7470
|
+
function perPortalPath(resolvedOut, portalId) {
|
|
7471
|
+
const ext = path5.extname(resolvedOut);
|
|
7472
|
+
const stem = ext ? resolvedOut.slice(0, -ext.length) : resolvedOut;
|
|
7473
|
+
return `${stem}.${safeFilenamePart(portalId)}${ext}`;
|
|
6732
7474
|
}
|
|
6733
|
-
function writeSurfaceFile(outPath,
|
|
7475
|
+
function writeSurfaceFile(outPath, snapshot, renderedSet) {
|
|
6734
7476
|
const resolved = path5.resolve(outPath);
|
|
6735
7477
|
fs4.mkdirSync(path5.dirname(resolved), { recursive: true });
|
|
6736
|
-
if (
|
|
6737
|
-
|
|
6738
|
-
|
|
7478
|
+
if (!renderedSet || renderedSet.length === 0) {
|
|
7479
|
+
writeYamlFile(resolved, snapshot);
|
|
7480
|
+
return [resolved];
|
|
7481
|
+
}
|
|
7482
|
+
if (renderedSet.length === 1) {
|
|
7483
|
+
fs4.writeFileSync(resolved, renderedSet[0].document);
|
|
7484
|
+
return [resolved];
|
|
7485
|
+
}
|
|
7486
|
+
return renderedSet.map((spec) => {
|
|
7487
|
+
const target = perPortalPath(resolved, spec.portalId);
|
|
7488
|
+
fs4.writeFileSync(target, spec.document);
|
|
7489
|
+
return target;
|
|
7490
|
+
});
|
|
6739
7491
|
}
|
|
6740
|
-
function
|
|
6741
|
-
const
|
|
6742
|
-
const openapi = format === "openapi" ? renderOpenApiForms(snapshot) : void 0;
|
|
6743
|
-
const body = openapi?.rendered ?? openapi?.renderedSet[0]?.document;
|
|
6744
|
-
const writtenTo = outPath ? writeSurfaceFile(outPath, body, snapshot) : void 0;
|
|
7492
|
+
function exportResult(snapshot, renderedSet, writtenPaths) {
|
|
7493
|
+
const rendered = renderedSet?.length === 1 ? renderedSet[0].document : void 0;
|
|
6745
7494
|
return {
|
|
6746
7495
|
snapshot,
|
|
6747
|
-
...
|
|
6748
|
-
...
|
|
6749
|
-
...
|
|
7496
|
+
...rendered !== void 0 ? { rendered } : {},
|
|
7497
|
+
...renderedSet ? { renderedSet } : {},
|
|
7498
|
+
...writtenPaths.length === 1 ? { writtenTo: writtenPaths[0] } : {},
|
|
7499
|
+
...writtenPaths.length ? { writtenPaths } : {}
|
|
6750
7500
|
};
|
|
6751
7501
|
}
|
|
7502
|
+
function exportSurface(maxAudience, format, outPath, portalId) {
|
|
7503
|
+
const snapshot = projectOwnSurface(maxAudience);
|
|
7504
|
+
let renderedSet = format === "openapi" ? toOpenApiSet(snapshot) : void 0;
|
|
7505
|
+
if (renderedSet && portalId) renderedSet = selectPortalSpec(renderedSet, portalId);
|
|
7506
|
+
const writtenPaths = outPath ? writeSurfaceFile(outPath, snapshot, renderedSet) : [];
|
|
7507
|
+
return exportResult(snapshot, renderedSet, writtenPaths);
|
|
7508
|
+
}
|
|
6752
7509
|
function importSurface(sourcePath, origin) {
|
|
6753
7510
|
const resolved = path5.resolve(sourcePath);
|
|
6754
7511
|
if (!fs4.existsSync(resolved)) {
|
|
@@ -6768,17 +7525,54 @@ function importSurface(sourcePath, origin) {
|
|
|
6768
7525
|
return snapshot;
|
|
6769
7526
|
}
|
|
6770
7527
|
function generateChildSnapshots(rootDir = getProjectRoot()) {
|
|
6771
|
-
const
|
|
7528
|
+
const topLevel = loadSubsystemSpecs().filter((s) => !s.id.includes("::"));
|
|
7529
|
+
const children = topLevel.filter((s) => s.projectPath);
|
|
6772
7530
|
if (!children.length) return [];
|
|
6773
|
-
const
|
|
7531
|
+
const familySnapshot = projectChildSurface();
|
|
7532
|
+
const siblingSnapshots = /* @__PURE__ */ new Map();
|
|
7533
|
+
const siblingSurface = (subsystemId) => {
|
|
7534
|
+
let snap = siblingSnapshots.get(subsystemId);
|
|
7535
|
+
if (!snap) {
|
|
7536
|
+
snap = projectSubsystemSurface(subsystemId);
|
|
7537
|
+
siblingSnapshots.set(subsystemId, snap);
|
|
7538
|
+
}
|
|
7539
|
+
return snap;
|
|
7540
|
+
};
|
|
6774
7541
|
const written = [];
|
|
6775
7542
|
for (const child of children) {
|
|
6776
7543
|
const childDir = path5.resolve(rootDir, child.projectPath);
|
|
6777
7544
|
if (!fs4.existsSync(childDir)) continue;
|
|
6778
|
-
written.push(saveSnapshot(
|
|
7545
|
+
written.push(saveSnapshot(familySnapshot, childDir));
|
|
7546
|
+
for (const sibling of topLevel) {
|
|
7547
|
+
if (sibling.id === child.id) continue;
|
|
7548
|
+
written.push(saveSnapshot(siblingSurface(sibling.id), childDir));
|
|
7549
|
+
}
|
|
6779
7550
|
}
|
|
6780
7551
|
return written;
|
|
6781
7552
|
}
|
|
7553
|
+
function computeParentStateId(parentRoot) {
|
|
7554
|
+
return computeStateIdAt(parentRoot);
|
|
7555
|
+
}
|
|
7556
|
+
function listExternalInterfaces() {
|
|
7557
|
+
const snapshots = listSnapshots();
|
|
7558
|
+
const chainingParent = resolveChainingParent();
|
|
7559
|
+
const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
|
|
7560
|
+
return snapshots.map((snapshot) => {
|
|
7561
|
+
const generated = snapshot.origin === "generated";
|
|
7562
|
+
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
7563
|
+
const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
|
|
7564
|
+
return {
|
|
7565
|
+
projectName: snapshot.projectName,
|
|
7566
|
+
origin: snapshot.origin,
|
|
7567
|
+
sourceKind,
|
|
7568
|
+
generatedAt: snapshot.generatedAt,
|
|
7569
|
+
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
7570
|
+
...snapshot.version ? { version: snapshot.version } : {},
|
|
7571
|
+
freshness,
|
|
7572
|
+
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
7573
|
+
};
|
|
7574
|
+
});
|
|
7575
|
+
}
|
|
6782
7576
|
function surfaceContentKey(snapshot) {
|
|
6783
7577
|
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
6784
7578
|
return JSON.stringify(content);
|
|
@@ -6805,7 +7599,7 @@ function checkChildSurfaceFreshness(rootDir = getProjectRoot()) {
|
|
|
6805
7599
|
}
|
|
6806
7600
|
return issues;
|
|
6807
7601
|
}
|
|
6808
|
-
var fs4, path5, SURFACES_DIRNAME;
|
|
7602
|
+
var fs4, path5, SURFACES_DIRNAME, CROSS_BOUNDARY_TARGETS;
|
|
6809
7603
|
var init_surfaces = __esm({
|
|
6810
7604
|
"src/core/surfaces.ts"() {
|
|
6811
7605
|
"use strict";
|
|
@@ -6813,12 +7607,14 @@ var init_surfaces = __esm({
|
|
|
6813
7607
|
path5 = __toESM(require("path"));
|
|
6814
7608
|
init_fs();
|
|
6815
7609
|
init_yaml();
|
|
7610
|
+
init_filenames();
|
|
6816
7611
|
init_models();
|
|
6817
7612
|
init_specs2();
|
|
6818
7613
|
init_statehash();
|
|
6819
7614
|
init_type_analysis();
|
|
6820
7615
|
init_openapi();
|
|
6821
7616
|
SURFACES_DIRNAME = "surfaces";
|
|
7617
|
+
CROSS_BOUNDARY_TARGETS = /* @__PURE__ */ new Set(["Portal", "Gateway", "Observer"]);
|
|
6822
7618
|
}
|
|
6823
7619
|
});
|
|
6824
7620
|
|
|
@@ -6986,7 +7782,8 @@ var init_contracts = __esm({
|
|
|
6986
7782
|
"SURFACE_REF_NOT_EXPOSED",
|
|
6987
7783
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
6988
7784
|
impl.id,
|
|
6989
|
-
isDraftCtx
|
|
7785
|
+
isDraftCtx,
|
|
7786
|
+
true
|
|
6990
7787
|
);
|
|
6991
7788
|
}
|
|
6992
7789
|
continue;
|
|
@@ -7043,7 +7840,8 @@ var init_contracts = __esm({
|
|
|
7043
7840
|
"SURFACE_REF_NOT_EXPOSED",
|
|
7044
7841
|
`Method "${implMethod.name}" in implementation "${impl.id}" calls "${step.targetMethod}" on cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
7045
7842
|
impl.id,
|
|
7046
|
-
isDraftCtx
|
|
7843
|
+
isDraftCtx,
|
|
7844
|
+
true
|
|
7047
7845
|
);
|
|
7048
7846
|
} else if (step.assertsGuarantees) {
|
|
7049
7847
|
const declared = new Set(surfaceMethod.guarantees ?? []);
|
|
@@ -7054,7 +7852,8 @@ var init_contracts = __esm({
|
|
|
7054
7852
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
7055
7853
|
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" asserts guarantee "${g}", but the surface snapshot of "${resolved.snapshot.projectName}" does not declare it on "${resolved.entry.id}.${step.targetMethod}".`,
|
|
7056
7854
|
impl.id,
|
|
7057
|
-
isDraftCtx
|
|
7855
|
+
isDraftCtx,
|
|
7856
|
+
true
|
|
7058
7857
|
);
|
|
7059
7858
|
}
|
|
7060
7859
|
}
|
|
@@ -8405,13 +9204,14 @@ var init_portals = __esm({
|
|
|
8405
9204
|
};
|
|
8406
9205
|
portalsRule = {
|
|
8407
9206
|
name: "portal-endpoints",
|
|
8408
|
-
description: "A Portal declares its portalType and binds every interface method to a concrete endpoint of the matching transport. Non-Portal components carry no portalType, basePath, or
|
|
9207
|
+
description: "A Portal declares its portalType and binds every interface method to a concrete endpoint of the matching transport. Non-Portal components carry no portalType, basePath, endpoints, or auth (auth is inbound transport auth \u2014 a Gateway carries it on the Portal it owns).",
|
|
8409
9208
|
codes: [
|
|
8410
9209
|
{ code: "MISSING_PORTAL_TYPE", defaultSeverity: "error", summary: "Portal without a portalType" },
|
|
8411
9210
|
{ code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
|
|
8412
9211
|
{ code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" },
|
|
8413
9212
|
{ code: "UNEXPECTED_PORTAL_FIELD", defaultSeverity: "error", summary: "Non-Portal component with portalType/basePath" },
|
|
8414
|
-
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
9213
|
+
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" },
|
|
9214
|
+
{ code: "AUTH_ON_NON_PORTAL", defaultSeverity: "warning", summary: "Non-Portal component declaring auth (auth is inbound transport auth, only meaningful on a Portal)" }
|
|
8415
9215
|
],
|
|
8416
9216
|
check(ctx) {
|
|
8417
9217
|
for (const comp of ctx.components) {
|
|
@@ -8463,6 +9263,15 @@ var init_portals = __esm({
|
|
|
8463
9263
|
isDraftCtx
|
|
8464
9264
|
);
|
|
8465
9265
|
}
|
|
9266
|
+
if (comp.auth !== void 0) {
|
|
9267
|
+
ctx.addIssue(
|
|
9268
|
+
"warning",
|
|
9269
|
+
"AUTH_ON_NON_PORTAL",
|
|
9270
|
+
`Component "${comp.id}" is a ${comp.componentType}, not a Portal, but declares "auth". Auth is inbound transport auth and is only meaningful on a Portal (a Gateway carries it on the Portal it owns). Move it to the exposed Portal, or remove it.`,
|
|
9271
|
+
comp.id,
|
|
9272
|
+
isDraftCtx
|
|
9273
|
+
);
|
|
9274
|
+
}
|
|
8466
9275
|
const compInterfaces = ctx.interfaces.filter((i) => i.component === comp.id);
|
|
8467
9276
|
for (const intf of compInterfaces) {
|
|
8468
9277
|
for (const m of intf.methods) {
|
|
@@ -8532,7 +9341,8 @@ var init_stereotype_deps = __esm({
|
|
|
8532
9341
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
8533
9342
|
`Boundary violation: ${comp.componentType} "${comp.id}" depends directly on "${depId}", a surface of project "${resolved.snapshot.projectName}". Only a local client Adapter may cross a project boundary \u2014 route this hop through an Adapter.`,
|
|
8534
9343
|
comp.id,
|
|
8535
|
-
isDraftCtx
|
|
9344
|
+
isDraftCtx,
|
|
9345
|
+
true
|
|
8536
9346
|
);
|
|
8537
9347
|
}
|
|
8538
9348
|
continue;
|
|
@@ -9110,14 +9920,14 @@ var init_declarative_assertions = __esm({
|
|
|
9110
9920
|
});
|
|
9111
9921
|
|
|
9112
9922
|
// src/core/rules/profiles.ts
|
|
9113
|
-
var BACKEND_LIKE, FRONTEND_LIKE,
|
|
9923
|
+
var BACKEND_LIKE, FRONTEND_LIKE, PROJECT_KINDS2, profilesRule;
|
|
9114
9924
|
var init_profiles = __esm({
|
|
9115
9925
|
"src/core/rules/profiles.ts"() {
|
|
9116
9926
|
"use strict";
|
|
9117
9927
|
init_types();
|
|
9118
9928
|
BACKEND_LIKE = /* @__PURE__ */ new Set(["backend", "lowlevel-os", "game-ecs", "realtime-embedded", "plc-cyclic"]);
|
|
9119
9929
|
FRONTEND_LIKE = /* @__PURE__ */ new Set(["frontend-reactive", "frontend-controller"]);
|
|
9120
|
-
|
|
9930
|
+
PROJECT_KINDS2 = new Set(PROJECT_KINDS);
|
|
9121
9931
|
profilesRule = {
|
|
9122
9932
|
name: "architectural-profiles",
|
|
9123
9933
|
description: "Per-profile stereotype constraints: View/FeatureComponent/RouterComponent only in frontend profiles; Actor/Supervisor forbidden in plc-cyclic (single scan cycle); Actor/Supervisor in frontend profiles warned. Extension packs may register custom profiles (family + forbidden/discouraged stereotype lists); unknown profile names are flagged.",
|
|
@@ -9141,7 +9951,7 @@ var init_profiles = __esm({
|
|
|
9141
9951
|
);
|
|
9142
9952
|
}
|
|
9143
9953
|
}
|
|
9144
|
-
if (!registered.has(ctx.projectType) && !
|
|
9954
|
+
if (!registered.has(ctx.projectType) && !PROJECT_KINDS2.has(ctx.projectType)) {
|
|
9145
9955
|
ctx.addIssue(
|
|
9146
9956
|
"warning",
|
|
9147
9957
|
"UNKNOWN_PROFILE",
|
|
@@ -11546,6 +12356,95 @@ var init_lint_allows = __esm({
|
|
|
11546
12356
|
}
|
|
11547
12357
|
});
|
|
11548
12358
|
|
|
12359
|
+
// src/core/rules/portal-call-auth.ts
|
|
12360
|
+
var COMPONENT_REF_PREFIX, portalCallAuthRule;
|
|
12361
|
+
var init_portal_call_auth = __esm({
|
|
12362
|
+
"src/core/rules/portal-call-auth.ts"() {
|
|
12363
|
+
"use strict";
|
|
12364
|
+
COMPONENT_REF_PREFIX = "component:";
|
|
12365
|
+
portalCallAuthRule = {
|
|
12366
|
+
name: "portal-call-auth",
|
|
12367
|
+
description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. When `auth.from` is a modeled reference (`component:<id>`) it must resolve to an Adapter/Store the presenter is wired to (UNKNOWN_AUTH_SOURCE / AUTH_SOURCE_NOT_PROVIDER / AUTH_SOURCE_UNWIRED). The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
|
|
12368
|
+
codes: [
|
|
12369
|
+
{ code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
|
|
12370
|
+
{ code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" },
|
|
12371
|
+
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
12372
|
+
{ code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
|
|
12373
|
+
{ code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
|
|
12374
|
+
],
|
|
12375
|
+
check(ctx) {
|
|
12376
|
+
for (const impl of ctx.implementations) {
|
|
12377
|
+
const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
|
|
12378
|
+
const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
|
|
12379
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
12380
|
+
for (const method of impl.methods ?? []) {
|
|
12381
|
+
for (const step of method.narrative ?? []) {
|
|
12382
|
+
if (step.type !== "call") continue;
|
|
12383
|
+
if (!step.targetComponent || step.targetComponent === ownComponent) continue;
|
|
12384
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
12385
|
+
if (!target || target.componentType !== "Portal") continue;
|
|
12386
|
+
if (!target.auth || target.auth.scheme === "none") continue;
|
|
12387
|
+
if (presenter && presenter.componentType !== "Adapter") {
|
|
12388
|
+
ctx.addIssue(
|
|
12389
|
+
"warning",
|
|
12390
|
+
"AUTH_PRESENTER_NOT_ADAPTER",
|
|
12391
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" (${presenter.componentType}) authenticates an outbound call to portal "${target.id}". An authenticated cross-service call is external I/O and must be made by an Adapter (the only block that does external I/O) \u2014 route it through a client Adapter.`,
|
|
12392
|
+
impl.id,
|
|
12393
|
+
draft
|
|
12394
|
+
);
|
|
12395
|
+
}
|
|
12396
|
+
const from = step.auth?.from;
|
|
12397
|
+
if (!from) {
|
|
12398
|
+
ctx.addIssue(
|
|
12399
|
+
"warning",
|
|
12400
|
+
"PORTAL_AUTH_UNMET",
|
|
12401
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}" calls the authenticated portal "${target.id}" (auth scheme: ${target.auth.scheme}) but does not declare where its credential is loaded from. Add the credential source (the step's auth.from \u2014 a secret-store component, env var, config key, or vault ref), or drop the portal's auth if it needs none.`,
|
|
12402
|
+
impl.id,
|
|
12403
|
+
draft
|
|
12404
|
+
);
|
|
12405
|
+
continue;
|
|
12406
|
+
}
|
|
12407
|
+
if (from.startsWith(COMPONENT_REF_PREFIX)) {
|
|
12408
|
+
const srcId = from.slice(COMPONENT_REF_PREFIX.length);
|
|
12409
|
+
const src = ctx.componentMap.get(srcId);
|
|
12410
|
+
if (!src) {
|
|
12411
|
+
ctx.addIssue(
|
|
12412
|
+
"warning",
|
|
12413
|
+
"UNKNOWN_AUTH_SOURCE",
|
|
12414
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
|
|
12415
|
+
impl.id,
|
|
12416
|
+
draft
|
|
12417
|
+
);
|
|
12418
|
+
} else {
|
|
12419
|
+
if (src.componentType !== "Adapter" && src.componentType !== "Store") {
|
|
12420
|
+
ctx.addIssue(
|
|
12421
|
+
"warning",
|
|
12422
|
+
"AUTH_SOURCE_NOT_PROVIDER",
|
|
12423
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
|
|
12424
|
+
impl.id,
|
|
12425
|
+
draft
|
|
12426
|
+
);
|
|
12427
|
+
}
|
|
12428
|
+
const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
|
|
12429
|
+
if (presenter && !wired) {
|
|
12430
|
+
ctx.addIssue(
|
|
12431
|
+
"warning",
|
|
12432
|
+
"AUTH_SOURCE_UNWIRED",
|
|
12433
|
+
`Narrative step ${step.stepNumber} of "${method.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
|
|
12434
|
+
impl.id,
|
|
12435
|
+
draft
|
|
12436
|
+
);
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
}
|
|
12440
|
+
}
|
|
12441
|
+
}
|
|
12442
|
+
}
|
|
12443
|
+
}
|
|
12444
|
+
};
|
|
12445
|
+
}
|
|
12446
|
+
});
|
|
12447
|
+
|
|
11549
12448
|
// src/core/rules/index.ts
|
|
11550
12449
|
function composeRuleSequence(extraRules = []) {
|
|
11551
12450
|
const base = SDD_RULES.filter((r) => r !== lintAllowsRule);
|
|
@@ -11701,9 +12600,15 @@ function buildRuleContext(opts) {
|
|
|
11701
12600
|
...[...SDD_RULES, ...extensions.rules].flatMap((r) => r.codes.map((c) => c.code)),
|
|
11702
12601
|
// Declarative assertions bring their own namespaced codes — lint.allow
|
|
11703
12602
|
// and severity overrides treat them exactly like builtins.
|
|
11704
|
-
...extensions.assertions.map((a) => a.fullCode)
|
|
12603
|
+
...extensions.assertions.map((a) => a.fullCode),
|
|
12604
|
+
// Entry-point emitted codes: validateSddTree's chained-subproject pass
|
|
12605
|
+
// raises these AFTER the rule run (it post-processes the aggregated issue
|
|
12606
|
+
// list), so no registered rule declares them — but lint.allow validation
|
|
12607
|
+
// must still recognize them as real codes.
|
|
12608
|
+
"CHAINED_SUBPROJECT_CONTEXT",
|
|
12609
|
+
"UNVERIFIED_EXTERNAL_REF"
|
|
11705
12610
|
]);
|
|
11706
|
-
const addIssue = (defaultSeverity, code, message, specId, isDraftContext) => {
|
|
12611
|
+
const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved) => {
|
|
11707
12612
|
if (scopeSubsystem && specId && !isSpecInScope(specId)) {
|
|
11708
12613
|
return;
|
|
11709
12614
|
}
|
|
@@ -11721,7 +12626,14 @@ function buildRuleContext(opts) {
|
|
|
11721
12626
|
if (severity === "warning") return;
|
|
11722
12627
|
}
|
|
11723
12628
|
}
|
|
11724
|
-
issues.push({
|
|
12629
|
+
issues.push({
|
|
12630
|
+
severity,
|
|
12631
|
+
code,
|
|
12632
|
+
message,
|
|
12633
|
+
specId,
|
|
12634
|
+
...isDraftContext ? { draftContext: true } : {},
|
|
12635
|
+
...surfaceResolved ? { surfaceResolved: true } : {}
|
|
12636
|
+
});
|
|
11725
12637
|
};
|
|
11726
12638
|
return {
|
|
11727
12639
|
system,
|
|
@@ -11795,6 +12707,7 @@ var init_rules = __esm({
|
|
|
11795
12707
|
init_hidden_state();
|
|
11796
12708
|
init_dependency_conformance();
|
|
11797
12709
|
init_lint_allows();
|
|
12710
|
+
init_portal_call_auth();
|
|
11798
12711
|
init_source_analysis();
|
|
11799
12712
|
init_types();
|
|
11800
12713
|
init_type_analysis();
|
|
@@ -11816,6 +12729,9 @@ var init_rules = __esm({
|
|
|
11816
12729
|
narrativeAntipatternsRule,
|
|
11817
12730
|
narrativeDetailRule,
|
|
11818
12731
|
portalsRule,
|
|
12732
|
+
// Cross-call auth: a narrative call into an authed Portal must name its
|
|
12733
|
+
// credential source (rides with the portal family).
|
|
12734
|
+
portalCallAuthRule,
|
|
11819
12735
|
stereotypeDepsRule,
|
|
11820
12736
|
patternsRule,
|
|
11821
12737
|
// Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
|
|
@@ -11878,6 +12794,7 @@ var init_rules = __esm({
|
|
|
11878
12794
|
// L4 expectations: implementations and their code linkage.
|
|
11879
12795
|
MISSING_IMPLEMENTATION_METHOD: "implementations",
|
|
11880
12796
|
MISSING_SOURCE_PATH: "implementations",
|
|
12797
|
+
PORTAL_AUTH_UNMET: "implementations",
|
|
11881
12798
|
MISSING_SOURCE_FILE: "implementations",
|
|
11882
12799
|
SOURCE_PATH_ESCAPES_ROOT: "implementations",
|
|
11883
12800
|
UNREALIZED_METHOD: "implementations",
|
|
@@ -12194,24 +13111,54 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
12194
13111
|
for (const rule of ruleSequence()) {
|
|
12195
13112
|
rule.check(ctx);
|
|
12196
13113
|
}
|
|
12197
|
-
const hasCrossTreeSuspects = issues.some(
|
|
13114
|
+
const hasCrossTreeSuspects = issues.some(
|
|
13115
|
+
(i) => SUBPROJECT_REFERENCE_CODES.has(i.code) || SUBPROJECT_CONFORMANCE_CODES.has(i.code)
|
|
13116
|
+
);
|
|
12198
13117
|
const chainingParent = hasCrossTreeSuspects ? findChainingParent(getProjectRoot()) : null;
|
|
12199
13118
|
if (chainingParent) {
|
|
13119
|
+
let unverified = 0;
|
|
12200
13120
|
let downgraded = 0;
|
|
12201
|
-
for (
|
|
12202
|
-
|
|
12203
|
-
if (iss.
|
|
12204
|
-
|
|
12205
|
-
|
|
13121
|
+
for (let at = 0; at < issues.length; at++) {
|
|
13122
|
+
const iss = issues[at];
|
|
13123
|
+
if (SUBPROJECT_REFERENCE_CODES.has(iss.code) && !iss.surfaceResolved) {
|
|
13124
|
+
issues[at] = {
|
|
13125
|
+
severity: "warning",
|
|
13126
|
+
code: "UNVERIFIED_EXTERNAL_REF",
|
|
13127
|
+
crossTreeContext: true,
|
|
13128
|
+
// --ci waives it (parent root is authoritative)
|
|
13129
|
+
specId: iss.specId,
|
|
13130
|
+
...iss.agentId ? { agentId: iss.agentId } : {},
|
|
13131
|
+
...iss.draftContext ? { draftContext: true } : {},
|
|
13132
|
+
message: `Unverified external reference (${iss.code}): ${iss.message} No vendored surface snapshot covers this reference, so it cannot be verified from this chained subproject standalone \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect what this project can consume via \`wairon surface externals\` / sdd_list_external_interfaces.`
|
|
13133
|
+
};
|
|
13134
|
+
unverified++;
|
|
13135
|
+
continue;
|
|
13136
|
+
}
|
|
13137
|
+
if (SUBPROJECT_CONFORMANCE_CODES.has(iss.code)) {
|
|
13138
|
+
if (iss.severity === "error") {
|
|
13139
|
+
iss.severity = "warning";
|
|
13140
|
+
downgraded++;
|
|
13141
|
+
}
|
|
13142
|
+
iss.crossTreeContext = true;
|
|
12206
13143
|
}
|
|
12207
|
-
iss.crossTreeContext = true;
|
|
12208
13144
|
}
|
|
12209
|
-
if (downgraded > 0) {
|
|
13145
|
+
if (unverified > 0 || downgraded > 0) {
|
|
13146
|
+
const notes = [];
|
|
13147
|
+
if (unverified > 0) {
|
|
13148
|
+
notes.push(
|
|
13149
|
+
`${unverified} cross-tree reference(s) have no vendored surface snapshot covering them and were reported as UNVERIFIED_EXTERNAL_REF warnings \u2014 re-lock the parent so fresh family/sibling snapshots ship, or inspect via \`wairon surface externals\` / sdd_list_external_interfaces.`
|
|
13150
|
+
);
|
|
13151
|
+
}
|
|
13152
|
+
if (downgraded > 0) {
|
|
13153
|
+
notes.push(
|
|
13154
|
+
`${downgraded} code\u2194spec conformance finding(s) (parent-root-relative source paths) were downgraded to warnings.`
|
|
13155
|
+
);
|
|
13156
|
+
}
|
|
12210
13157
|
issues.unshift({
|
|
12211
13158
|
severity: "warning",
|
|
12212
13159
|
code: "CHAINED_SUBPROJECT_CONTEXT",
|
|
12213
13160
|
crossTreeContext: true,
|
|
12214
|
-
message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${
|
|
13161
|
+
message: `This project is a chained subproject ("${chainingParent.subsystemId}") of the parent project at "${chainingParent.parentRoot}". ${notes.join(" ")} Full cross-tree verification runs from the parent root.`
|
|
12215
13162
|
});
|
|
12216
13163
|
}
|
|
12217
13164
|
}
|
|
@@ -12228,7 +13175,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
12228
13175
|
function validateAsComplete(options) {
|
|
12229
13176
|
return validateSddTree({ ...options ?? {}, treatAllAsComplete: true });
|
|
12230
13177
|
}
|
|
12231
|
-
var
|
|
13178
|
+
var SUBPROJECT_REFERENCE_CODES, SUBPROJECT_CONFORMANCE_CODES;
|
|
12232
13179
|
var init_validation = __esm({
|
|
12233
13180
|
"src/core/validation.ts"() {
|
|
12234
13181
|
"use strict";
|
|
@@ -12241,8 +13188,7 @@ var init_validation = __esm({
|
|
|
12241
13188
|
init_source_analysis();
|
|
12242
13189
|
init_specs2();
|
|
12243
13190
|
init_fs();
|
|
12244
|
-
|
|
12245
|
-
// reference resolution
|
|
13191
|
+
SUBPROJECT_REFERENCE_CODES = /* @__PURE__ */ new Set([
|
|
12246
13192
|
"UNDEFINED_TYPE_REFERENCE",
|
|
12247
13193
|
"INVALID_DEPENDENCY_REFERENCE",
|
|
12248
13194
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
@@ -12250,8 +13196,9 @@ var init_validation = __esm({
|
|
|
12250
13196
|
"UNDECLARED_DEPENDENCY_CALL",
|
|
12251
13197
|
"INVALID_TRUSTED_LINK",
|
|
12252
13198
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
12253
|
-
"CROSS_TREE_REF_UNRESOLVED"
|
|
12254
|
-
|
|
13199
|
+
"CROSS_TREE_REF_UNRESOLVED"
|
|
13200
|
+
]);
|
|
13201
|
+
SUBPROJECT_CONFORMANCE_CODES = /* @__PURE__ */ new Set([
|
|
12255
13202
|
"MISSING_SOURCE_FILE",
|
|
12256
13203
|
"SOURCE_PATH_ESCAPES_ROOT",
|
|
12257
13204
|
"MISSING_SOURCE_PATH",
|
|
@@ -13174,6 +14121,19 @@ function dryRunSerializeSpecs(include) {
|
|
|
13174
14121
|
function buildProjectGraph(level) {
|
|
13175
14122
|
return buildGraphModel(level);
|
|
13176
14123
|
}
|
|
14124
|
+
function resolveChainingParent() {
|
|
14125
|
+
return findChainingParent(getProjectRoot());
|
|
14126
|
+
}
|
|
14127
|
+
function computeStateIdAt(root) {
|
|
14128
|
+
const resolved = path10.resolve(root);
|
|
14129
|
+
return runWithProjectRoot(resolved, () => {
|
|
14130
|
+
workspaceFor(resolved).invalidate();
|
|
14131
|
+
const system = loadSystemSpec();
|
|
14132
|
+
if (!system) return null;
|
|
14133
|
+
const s = computeStateId();
|
|
14134
|
+
return `${s.algorithm}:${s.digest}`;
|
|
14135
|
+
});
|
|
14136
|
+
}
|
|
13177
14137
|
function deleteTypeSpec(id) {
|
|
13178
14138
|
return current().deleteTypeSpec(id);
|
|
13179
14139
|
}
|
|
@@ -13217,6 +14177,7 @@ var init_specs2 = __esm({
|
|
|
13217
14177
|
path10 = __toESM(require("path"));
|
|
13218
14178
|
init_loader();
|
|
13219
14179
|
init_fs();
|
|
14180
|
+
init_statehash();
|
|
13220
14181
|
init_yaml();
|
|
13221
14182
|
init_models();
|
|
13222
14183
|
init_narrative_labels();
|
|
@@ -15311,6 +16272,7 @@ __export(src_exports, {
|
|
|
15311
16272
|
OutputTargetSchema: () => OutputTargetSchema,
|
|
15312
16273
|
PACK_DIR_ENTRIES: () => PACK_DIR_ENTRIES,
|
|
15313
16274
|
PATTERN_TYPES: () => PATTERN_TYPES,
|
|
16275
|
+
PROJECT_KINDS: () => PROJECT_KINDS,
|
|
15314
16276
|
PackAssertionSchema: () => PackAssertionSchema,
|
|
15315
16277
|
PackSkillSchema: () => PackSkillSchema,
|
|
15316
16278
|
ParallelBranchSchema: () => ParallelBranchSchema,
|
|
@@ -15321,8 +16283,10 @@ __export(src_exports, {
|
|
|
15321
16283
|
PortalAuthSchemeSchema: () => PortalAuthSchemeSchema,
|
|
15322
16284
|
PortalTypeSchema: () => PortalTypeSchema,
|
|
15323
16285
|
ProfileDefSchema: () => ProfileDefSchema,
|
|
16286
|
+
ProfileSelectionSubjectSchema: () => ProfileSelectionSubjectSchema,
|
|
15324
16287
|
ProjectConfigSchema: () => ProjectConfigSchema,
|
|
15325
16288
|
ProjectNotInitializedError: () => ProjectNotInitializedError,
|
|
16289
|
+
ProjectProfileSelectionSchema: () => ProjectProfileSelectionSchema,
|
|
15326
16290
|
PublicInterfaceSchema: () => PublicInterfaceSchema,
|
|
15327
16291
|
PublicInterfaceTypeSchema: () => PublicInterfaceTypeSchema,
|
|
15328
16292
|
RegistrySchema: () => RegistrySchema,
|
|
@@ -15381,7 +16345,9 @@ __export(src_exports, {
|
|
|
15381
16345
|
clearLoaderIssues: () => clearLoaderIssues,
|
|
15382
16346
|
collectPromotableSpecs: () => collectPromotableSpecs,
|
|
15383
16347
|
composeRuleSequence: () => composeRuleSequence,
|
|
16348
|
+
computeParentStateId: () => computeParentStateId,
|
|
15384
16349
|
computeStateId: () => computeStateId,
|
|
16350
|
+
computeStateIdAt: () => computeStateIdAt,
|
|
15385
16351
|
contextDir: () => contextDir,
|
|
15386
16352
|
createAgentRecord: () => createAgentRecord,
|
|
15387
16353
|
createChainedSubsystem: () => createChainedSubsystem,
|
|
@@ -15447,6 +16413,7 @@ __export(src_exports, {
|
|
|
15447
16413
|
isOpenApiDocument: () => isOpenApiDocument,
|
|
15448
16414
|
isProjectInitialized: () => isProjectInitialized,
|
|
15449
16415
|
listDirectChainedSubprojects: () => listDirectChainedSubprojects,
|
|
16416
|
+
listExternalInterfaces: () => listExternalInterfaces,
|
|
15450
16417
|
listFiles: () => listFiles,
|
|
15451
16418
|
listFilesRecursive: () => listFilesRecursive,
|
|
15452
16419
|
listFreeStandingDomains: () => listFreeStandingDomains,
|
|
@@ -15492,6 +16459,7 @@ __export(src_exports, {
|
|
|
15492
16459
|
pathExists: () => pathExists,
|
|
15493
16460
|
projectChildSurface: () => projectChildSurface,
|
|
15494
16461
|
projectOwnSurface: () => projectOwnSurface,
|
|
16462
|
+
projectSubsystemSurface: () => projectSubsystemSurface,
|
|
15495
16463
|
promoteAllComplete: () => promoteAllComplete,
|
|
15496
16464
|
provisionProject: () => provisionProject,
|
|
15497
16465
|
readArchitectureContext: () => readArchitectureContext,
|
|
@@ -15511,6 +16479,7 @@ __export(src_exports, {
|
|
|
15511
16479
|
renderTemplateInstructions: () => renderTemplateInstructions,
|
|
15512
16480
|
renderWaironGuide: () => renderWaironGuide,
|
|
15513
16481
|
resolveAgentTopology: () => resolveAgentTopology,
|
|
16482
|
+
resolveChainingParent: () => resolveChainingParent,
|
|
15514
16483
|
resolveDomains: () => resolveDomains,
|
|
15515
16484
|
resolvePackRef: () => resolvePackRef,
|
|
15516
16485
|
resolveSubprojectForNamespace: () => resolveSubprojectForNamespace,
|
|
@@ -15574,7 +16543,7 @@ function defaultTargetConfig(type) {
|
|
|
15574
16543
|
enabled: true
|
|
15575
16544
|
};
|
|
15576
16545
|
}
|
|
15577
|
-
var WAIRON_VERSION = "5.0
|
|
16546
|
+
var WAIRON_VERSION = "5.1.0";
|
|
15578
16547
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
15579
16548
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
15580
16549
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -16898,6 +17867,7 @@ init_yaml();
|
|
|
16898
17867
|
OutputTargetSchema,
|
|
16899
17868
|
PACK_DIR_ENTRIES,
|
|
16900
17869
|
PATTERN_TYPES,
|
|
17870
|
+
PROJECT_KINDS,
|
|
16901
17871
|
PackAssertionSchema,
|
|
16902
17872
|
PackSkillSchema,
|
|
16903
17873
|
ParallelBranchSchema,
|
|
@@ -16908,8 +17878,10 @@ init_yaml();
|
|
|
16908
17878
|
PortalAuthSchemeSchema,
|
|
16909
17879
|
PortalTypeSchema,
|
|
16910
17880
|
ProfileDefSchema,
|
|
17881
|
+
ProfileSelectionSubjectSchema,
|
|
16911
17882
|
ProjectConfigSchema,
|
|
16912
17883
|
ProjectNotInitializedError,
|
|
17884
|
+
ProjectProfileSelectionSchema,
|
|
16913
17885
|
PublicInterfaceSchema,
|
|
16914
17886
|
PublicInterfaceTypeSchema,
|
|
16915
17887
|
RegistrySchema,
|
|
@@ -16968,7 +17940,9 @@ init_yaml();
|
|
|
16968
17940
|
clearLoaderIssues,
|
|
16969
17941
|
collectPromotableSpecs,
|
|
16970
17942
|
composeRuleSequence,
|
|
17943
|
+
computeParentStateId,
|
|
16971
17944
|
computeStateId,
|
|
17945
|
+
computeStateIdAt,
|
|
16972
17946
|
contextDir,
|
|
16973
17947
|
createAgentRecord,
|
|
16974
17948
|
createChainedSubsystem,
|
|
@@ -17034,6 +18008,7 @@ init_yaml();
|
|
|
17034
18008
|
isOpenApiDocument,
|
|
17035
18009
|
isProjectInitialized,
|
|
17036
18010
|
listDirectChainedSubprojects,
|
|
18011
|
+
listExternalInterfaces,
|
|
17037
18012
|
listFiles,
|
|
17038
18013
|
listFilesRecursive,
|
|
17039
18014
|
listFreeStandingDomains,
|
|
@@ -17079,6 +18054,7 @@ init_yaml();
|
|
|
17079
18054
|
pathExists,
|
|
17080
18055
|
projectChildSurface,
|
|
17081
18056
|
projectOwnSurface,
|
|
18057
|
+
projectSubsystemSurface,
|
|
17082
18058
|
promoteAllComplete,
|
|
17083
18059
|
provisionProject,
|
|
17084
18060
|
readArchitectureContext,
|
|
@@ -17098,6 +18074,7 @@ init_yaml();
|
|
|
17098
18074
|
renderTemplateInstructions,
|
|
17099
18075
|
renderWaironGuide,
|
|
17100
18076
|
resolveAgentTopology,
|
|
18077
|
+
resolveChainingParent,
|
|
17101
18078
|
resolveDomains,
|
|
17102
18079
|
resolvePackRef,
|
|
17103
18080
|
resolveSubprojectForNamespace,
|