@wairon/cli 5.1.1-dev.67 → 5.1.1-dev.69
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 +192 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +177 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3458,6 +3458,7 @@ __export(src_exports, {
|
|
|
3458
3458
|
PatternRefSchema: () => PatternRefSchema,
|
|
3459
3459
|
PortalAuthSchema: () => PortalAuthSchema,
|
|
3460
3460
|
PortalAuthSchemeSchema: () => PortalAuthSchemeSchema,
|
|
3461
|
+
PortalMountSchema: () => PortalMountSchema,
|
|
3461
3462
|
PortalTypeSchema: () => PortalTypeSchema,
|
|
3462
3463
|
ProfileSelectionSubjectSchema: () => ProfileSelectionSubjectSchema,
|
|
3463
3464
|
ProjectConfigSchema: () => ProjectConfigSchema,
|
|
@@ -5369,6 +5370,24 @@ var PortalAuthSchema = import_zod8.z.object({
|
|
|
5369
5370
|
description: import_zod8.z.string().optional(),
|
|
5370
5371
|
example: import_zod8.z.string().optional()
|
|
5371
5372
|
});
|
|
5373
|
+
var PortalMountSchema = import_zod8.z.object({
|
|
5374
|
+
/** The mounted Portal's component id. */
|
|
5375
|
+
portal: import_zod8.z.string(),
|
|
5376
|
+
/**
|
|
5377
|
+
* The path prefixes this listener routes to the portal. A path lies under a
|
|
5378
|
+
* prefix when it EQUALS it or continues it past a slash — so `/` covers the
|
|
5379
|
+
* root itself and nothing else, which is what lets an app shell sit at `GET /`
|
|
5380
|
+
* without swallowing every other route.
|
|
5381
|
+
*/
|
|
5382
|
+
prefixes: import_zod8.z.array(import_zod8.z.string()),
|
|
5383
|
+
/**
|
|
5384
|
+
* The router entry the listener calls to hand the portal its request,
|
|
5385
|
+
* exported by the PORTAL's own file (so export-conformance holds that file to
|
|
5386
|
+
* it). Absent when the listener calls the portal's contract methods directly,
|
|
5387
|
+
* route by route — then there is no entry to name.
|
|
5388
|
+
*/
|
|
5389
|
+
via: import_zod8.z.string().optional()
|
|
5390
|
+
});
|
|
5372
5391
|
var ComponentSpecSchema = import_zod8.z.object({
|
|
5373
5392
|
id: SpecIdSchema,
|
|
5374
5393
|
name: import_zod8.z.string(),
|
|
@@ -5388,6 +5407,13 @@ var ComponentSpecSchema = import_zod8.z.object({
|
|
|
5388
5407
|
auth: PortalAuthSchema.optional(),
|
|
5389
5408
|
/** Portal-only: capability → component.method dispatch table (see DispatchBindingSchema). */
|
|
5390
5409
|
dispatch: import_zod8.z.array(DispatchBindingSchema).optional(),
|
|
5410
|
+
/**
|
|
5411
|
+
* Portal-only: the portals this LISTENER serves (see PortalMountSchema).
|
|
5412
|
+
* Declaring the field — even as an empty array — is what marks a portal as a
|
|
5413
|
+
* listener: the one kind of portal the host starts directly, so nothing else
|
|
5414
|
+
* needs to mount it. Absent means "a portal something else must serve".
|
|
5415
|
+
*/
|
|
5416
|
+
mounts: import_zod8.z.array(PortalMountSchema).optional(),
|
|
5391
5417
|
/** Store-only: whether held state survives restart (see DurabilitySchema). */
|
|
5392
5418
|
durability: DurabilitySchema.optional(),
|
|
5393
5419
|
/** Orchestrator-only: what the logic may depend on; unset means a workflow (see DependencyClassSchema). */
|
|
@@ -11567,7 +11593,7 @@ function defaultTargetConfig(type) {
|
|
|
11567
11593
|
enabled: true
|
|
11568
11594
|
};
|
|
11569
11595
|
}
|
|
11570
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
11596
|
+
var WAIRON_VERSION = "5.1.1-dev.69";
|
|
11571
11597
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
11572
11598
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
11573
11599
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -15331,6 +15357,84 @@ var lifecycleRule = {
|
|
|
15331
15357
|
}
|
|
15332
15358
|
};
|
|
15333
15359
|
|
|
15360
|
+
// src/core/rules/wiring/portal-mounts.ts
|
|
15361
|
+
function liesUnder(path28, prefix) {
|
|
15362
|
+
return path28 === prefix || path28.startsWith(prefix + "/");
|
|
15363
|
+
}
|
|
15364
|
+
var portalMountsRule = {
|
|
15365
|
+
name: "portal-mounts",
|
|
15366
|
+
description: "Which listener serves which portal: a portal's routes are its methods' endpoint bindings, but nothing said which listener hands it its requests, so a portal no listener serves was unreachable without anyone noticing, and the call that mounts one crossed a boundary no contract described. A listener Portal declares its mounts \u2014 each a portal, the path prefixes routed to it, and optionally the router entry it calls \u2014 and this rule checks them against the tree: a mount must name a Portal, every HTTP endpoint of a mounted portal must lie under one of its prefixes, and every portal with HTTP endpoints must be mounted by some listener unless it is one. A path lies under a prefix when it equals it or continues it past a slash, so `/` covers only the root. Whether the named entry is really exported is the code's question, and export-conformance asks it.",
|
|
15367
|
+
codes: [
|
|
15368
|
+
{
|
|
15369
|
+
code: "MOUNT_TARGET_NOT_PORTAL",
|
|
15370
|
+
defaultSeverity: "error",
|
|
15371
|
+
summary: "A listener mounts something that is not a Portal \u2014 an unknown component, or one whose type cannot serve routes"
|
|
15372
|
+
},
|
|
15373
|
+
{
|
|
15374
|
+
code: "ENDPOINT_OUTSIDE_MOUNT",
|
|
15375
|
+
defaultSeverity: "warning",
|
|
15376
|
+
summary: "A mounted portal declares an HTTP endpoint under none of the prefixes its listener routes to it \u2014 a route the contract promises that no request can reach"
|
|
15377
|
+
},
|
|
15378
|
+
{
|
|
15379
|
+
code: "UNMOUNTED_PORTAL",
|
|
15380
|
+
defaultSeverity: "warning",
|
|
15381
|
+
summary: "A portal declares HTTP endpoints but no listener mounts it and it declares no mounts of its own \u2014 a whole surface nothing serves"
|
|
15382
|
+
}
|
|
15383
|
+
],
|
|
15384
|
+
check(ctx) {
|
|
15385
|
+
const httpRoutesOf = (compId) => {
|
|
15386
|
+
const routes = [];
|
|
15387
|
+
for (const method of ctx.interfaceMethodsOf(compId)) {
|
|
15388
|
+
const ep = method.endpoint;
|
|
15389
|
+
if (ep?.transport === "HTTP") routes.push({ method: method.name, verb: ep.method, path: ep.path });
|
|
15390
|
+
}
|
|
15391
|
+
return routes;
|
|
15392
|
+
};
|
|
15393
|
+
const mounted = /* @__PURE__ */ new Set();
|
|
15394
|
+
for (const listener of ctx.components) {
|
|
15395
|
+
if (listener.componentType !== "Portal" || !listener.mounts) continue;
|
|
15396
|
+
const listenerDraft = ctx.isComponentDraft(listener.id);
|
|
15397
|
+
for (const mount of listener.mounts) {
|
|
15398
|
+
const target = ctx.componentMap.get(mount.portal);
|
|
15399
|
+
if (!target || target.componentType !== "Portal") {
|
|
15400
|
+
ctx.addIssue(
|
|
15401
|
+
"error",
|
|
15402
|
+
"MOUNT_TARGET_NOT_PORTAL",
|
|
15403
|
+
target ? `Listener "${listener.id}" mounts "${mount.portal}" under ${mount.prefixes.map((p) => `"${p}"`).join(", ") || "no prefixes"}, but "${target.id}" is a ${target.componentType} \u2014 only a Portal serves routes, so this mount routes requests to nothing.` : `Listener "${listener.id}" mounts "${mount.portal}" under ${mount.prefixes.map((p) => `"${p}"`).join(", ") || "no prefixes"}, but no component "${mount.portal}" exists \u2014 this mount routes requests to nothing.`,
|
|
15404
|
+
listener.id,
|
|
15405
|
+
listenerDraft || (target ? ctx.isComponentDraft(target.id) : false)
|
|
15406
|
+
);
|
|
15407
|
+
continue;
|
|
15408
|
+
}
|
|
15409
|
+
mounted.add(target.id);
|
|
15410
|
+
for (const route of httpRoutesOf(target.id)) {
|
|
15411
|
+
if (mount.prefixes.some((prefix) => liesUnder(route.path, prefix))) continue;
|
|
15412
|
+
ctx.addIssue(
|
|
15413
|
+
"warning",
|
|
15414
|
+
"ENDPOINT_OUTSIDE_MOUNT",
|
|
15415
|
+
`Portal "${target.id}" binds "${route.method}" to ${route.verb} ${route.path}, but listener "${listener.id}" routes it only ${mount.prefixes.map((p) => `"${p}"`).join(", ") || "no prefixes at all"} \u2014 a route the contract promises that no request through this listener can reach. A path lies under a prefix when it equals it or continues it past a slash. Add the prefix to the mount, or move the endpoint under one it already routes.`,
|
|
15416
|
+
listener.id,
|
|
15417
|
+
listenerDraft || ctx.isComponentDraft(target.id)
|
|
15418
|
+
);
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15421
|
+
}
|
|
15422
|
+
for (const portal of ctx.components) {
|
|
15423
|
+
if (portal.componentType !== "Portal") continue;
|
|
15424
|
+
if (portal.mounts !== void 0 || mounted.has(portal.id)) continue;
|
|
15425
|
+
const routes = httpRoutesOf(portal.id);
|
|
15426
|
+
if (routes.length === 0) continue;
|
|
15427
|
+
ctx.addIssue(
|
|
15428
|
+
"warning",
|
|
15429
|
+
"UNMOUNTED_PORTAL",
|
|
15430
|
+
`Portal "${portal.id}" declares ${routes.length} HTTP endpoint(s), but no listener mounts it and it declares no mounts of its own \u2014 a whole surface nothing serves. Mount it on the listener that routes its requests, or, if it IS a listener the host starts directly, declare its mounts (an empty list says it serves only its own routes).`,
|
|
15431
|
+
portal.id,
|
|
15432
|
+
ctx.isComponentDraft(portal.id)
|
|
15433
|
+
);
|
|
15434
|
+
}
|
|
15435
|
+
}
|
|
15436
|
+
};
|
|
15437
|
+
|
|
15334
15438
|
// src/core/rules/narrative-graph-projector.ts
|
|
15335
15439
|
var methodKey = (compId, methodName) => `${compId}#${methodName}`;
|
|
15336
15440
|
function stepEdges(step, ctx, followRegisterEdges) {
|
|
@@ -17109,7 +17213,7 @@ var unclaimedSourceRule = {
|
|
|
17109
17213
|
var NOTHING_PROMISED = /* @__PURE__ */ new Set();
|
|
17110
17214
|
var exportConformanceRule = {
|
|
17111
17215
|
name: "export-conformance",
|
|
17112
|
-
description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
|
|
17216
|
+
description: "Code-to-contract for the SURFACE: does this file publish anything the components it realizes never promised, that another component then takes? The rest of the conformance set reads contract-to-code \u2014 it asks whether the code holds what a spec claims \u2014 so a file could export whatever it liked under a component's name and nothing looked. That is one half of the same hole as a contract promising a parameter it never passes. A method's `exportedVia` names the export a consumer imports to REACH it \u2014 the value that composes it, which `symbol` cannot name because `symbol` names the function inside \u2014 and a listener's mount names the router entry it calls to hand a portal its requests; both are declared publication, and a declared handle the file does not actually export is itself a finding, so naming one can never become a free-text suppression. Both questions are asked only of a file read at exact grade, and a taker counts only when its own import specifier resolves to the file it is accused of taking from: below that nothing separates an export from a mention, or a shared word from a shared module.",
|
|
17113
17217
|
codes: [
|
|
17114
17218
|
{
|
|
17115
17219
|
code: "UNDECLARED_EXPORT",
|
|
@@ -17122,7 +17226,7 @@ var exportConformanceRule = {
|
|
|
17122
17226
|
{
|
|
17123
17227
|
code: "UNREALIZED_EXPORT_HANDLE",
|
|
17124
17228
|
defaultSeverity: "warning",
|
|
17125
|
-
summary: "
|
|
17229
|
+
summary: "A declared publication handle \u2014 a method's `exportedVia`, or the router entry a listener mounts a portal through \u2014 names an export its own source file does not have: the spec names a route to the code that nobody can import"
|
|
17126
17230
|
// Deliberately NOT carryable, and so it hands over no `parts`: a wrong
|
|
17127
17231
|
// handle is one string to correct, and a parking space for it could
|
|
17128
17232
|
// only ever be a parking space for a lie.
|
|
@@ -17168,6 +17272,30 @@ var exportConformanceRule = {
|
|
|
17168
17272
|
draftContext
|
|
17169
17273
|
);
|
|
17170
17274
|
}
|
|
17275
|
+
for (const listener of ctx.components) {
|
|
17276
|
+
if (listener.componentType !== "Portal") continue;
|
|
17277
|
+
for (const mount of listener.mounts ?? []) {
|
|
17278
|
+
const handle = mount.via;
|
|
17279
|
+
if (!handle) continue;
|
|
17280
|
+
const portal = ctx.componentMap.get(mount.portal);
|
|
17281
|
+
if (!portal) continue;
|
|
17282
|
+
const files = realization.filesOf(portal.id).map(pathKey);
|
|
17283
|
+
const exact = files.filter((file) => {
|
|
17284
|
+
const facts = code.factsAt(file);
|
|
17285
|
+
return !!facts && facts.status === "analyzed" && facts.analysisGrade === "exact";
|
|
17286
|
+
});
|
|
17287
|
+
const exporters = exact.filter((file) => code.factsAt(file).exportedNames.includes(handle));
|
|
17288
|
+
for (const file of exporters) promise(file, handle);
|
|
17289
|
+
if (exporters.length > 0 || exact.length === 0 || exact.length < files.length) continue;
|
|
17290
|
+
ctx.addIssue(
|
|
17291
|
+
"warning",
|
|
17292
|
+
"UNREALIZED_EXPORT_HANDLE",
|
|
17293
|
+
`Listener "${listener.id}" mounts portal "${portal.id}" through "${handle}", but ${files.map((f) => `"${f}"`).join(", ")} \u2014 the file(s) realizing "${portal.id}" \u2014 export no such name. A mount's \`via\` names the router entry the listener calls to hand the portal its requests, published by the portal's own files, so one they do not publish promises a route nobody can import. It allows nothing either: every export of those files is still read against the contracts. Name the entry the portal's file actually exports, export it, or drop \`via\` if the listener calls the portal's contract methods directly.`,
|
|
17294
|
+
listener.id,
|
|
17295
|
+
ctx.isComponentDraft(listener.id) || ctx.isComponentDraft(portal.id)
|
|
17296
|
+
);
|
|
17297
|
+
}
|
|
17298
|
+
}
|
|
17171
17299
|
for (const type of ctx.types) {
|
|
17172
17300
|
if (!type.sourcePath) continue;
|
|
17173
17301
|
promise(type.sourcePath, type.symbol ?? type.name);
|
|
@@ -17176,19 +17304,34 @@ var exportConformanceRule = {
|
|
|
17176
17304
|
}
|
|
17177
17305
|
}
|
|
17178
17306
|
const takenFrom = /* @__PURE__ */ new Map();
|
|
17307
|
+
const take = (consumer, writtenIn, specifier, names) => {
|
|
17308
|
+
const target = resolveImport(writtenIn, specifier, code.paths);
|
|
17309
|
+
if (!target) return;
|
|
17310
|
+
let byName = takenFrom.get(target);
|
|
17311
|
+
if (!byName) takenFrom.set(target, byName = /* @__PURE__ */ new Map());
|
|
17312
|
+
for (const name of names) {
|
|
17313
|
+
if (!name) continue;
|
|
17314
|
+
let takers = byName.get(name);
|
|
17315
|
+
if (!takers) byName.set(name, takers = /* @__PURE__ */ new Set());
|
|
17316
|
+
takers.add(consumer);
|
|
17317
|
+
}
|
|
17318
|
+
};
|
|
17179
17319
|
for (const consumer of realization.paths) {
|
|
17180
17320
|
if (!code.exactPaths.has(consumer)) continue;
|
|
17181
|
-
|
|
17321
|
+
const facts = code.factsAt(consumer);
|
|
17322
|
+
if (!facts) continue;
|
|
17323
|
+
for (const [local, binding] of Object.entries(facts.importBindings ?? {})) {
|
|
17182
17324
|
if (binding.namespace) continue;
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17190
|
-
if (!
|
|
17191
|
-
|
|
17325
|
+
take(consumer, consumer, binding.from, [local, binding.imported]);
|
|
17326
|
+
}
|
|
17327
|
+
for (const sites of Object.values(facts.functionCallSites ?? {})) {
|
|
17328
|
+
for (const site of sites) {
|
|
17329
|
+
if (!site.member || !site.via) continue;
|
|
17330
|
+
const writtenIn = site.from ? pathKey(site.from) : consumer;
|
|
17331
|
+
const scope = site.from ? code.factsAt(writtenIn) : facts;
|
|
17332
|
+
if (!scope) continue;
|
|
17333
|
+
const binding = importBindingOf(scope, site.via);
|
|
17334
|
+
if (binding?.namespace) take(consumer, writtenIn, binding.from, [site.name]);
|
|
17192
17335
|
}
|
|
17193
17336
|
}
|
|
17194
17337
|
}
|
|
@@ -18471,6 +18614,9 @@ var SDD_RULES = [
|
|
|
18471
18614
|
dispatchTableBindingsRule,
|
|
18472
18615
|
dispatchStepRoutingRule,
|
|
18473
18616
|
lifecycleRule,
|
|
18617
|
+
// Which listener serves which portal: another declared edge into a
|
|
18618
|
+
// component, so its validity reads beside dispatch and lifecycle.
|
|
18619
|
+
portalMountsRule,
|
|
18474
18620
|
// The declared entrypoint's own prose before the walk that its declaration
|
|
18475
18621
|
// silences, then the walk, then the types no walk can reach.
|
|
18476
18622
|
invokedByDescriptionRule,
|
|
@@ -21111,6 +21257,10 @@ function stripNamespaceFromComponent(spec, prefix) {
|
|
|
21111
21257
|
dispatch: spec.dispatch?.map((b) => ({
|
|
21112
21258
|
...b,
|
|
21113
21259
|
component: relativizeId(b.component, prefix)
|
|
21260
|
+
})),
|
|
21261
|
+
mounts: spec.mounts?.map((m) => ({
|
|
21262
|
+
...m,
|
|
21263
|
+
portal: relativizeId(m.portal, prefix)
|
|
21114
21264
|
}))
|
|
21115
21265
|
};
|
|
21116
21266
|
}
|
|
@@ -21261,6 +21411,7 @@ function rewriteSpecRefs(raw, remap) {
|
|
|
21261
21411
|
rewriteEach(raw.dependsOn, "component");
|
|
21262
21412
|
rewriteEach(raw.owns, "component");
|
|
21263
21413
|
for (const binding of entries(raw.dispatch)) rewritePair(binding, "component", "method");
|
|
21414
|
+
for (const mount of entries(raw.mounts)) rewrite(mount, "portal", "component");
|
|
21264
21415
|
} else if (kind === "subsystem") {
|
|
21265
21416
|
for (const entrypoint of entries(raw.lifecycle)) rewritePair(entrypoint, "component", "method");
|
|
21266
21417
|
rewritePublished(raw.publicInterfaces);
|
|
@@ -21315,6 +21466,9 @@ function identityKeyOf(field, item) {
|
|
|
21315
21466
|
switch (field) {
|
|
21316
21467
|
case "dispatch":
|
|
21317
21468
|
return str(o.capability);
|
|
21469
|
+
// A listener mounts each portal once, so the portal IS the mount's identity.
|
|
21470
|
+
case "mounts":
|
|
21471
|
+
return str(o.portal);
|
|
21318
21472
|
case "lifecycle":
|
|
21319
21473
|
return `${String(o.phase)} ${String(o.component)} ${String(o.method)}`;
|
|
21320
21474
|
case "emits":
|
|
@@ -21705,6 +21859,12 @@ var SpecWorkspace = class {
|
|
|
21705
21859
|
dispatch: comp.dispatch?.map((b) => ({
|
|
21706
21860
|
...b,
|
|
21707
21861
|
component: qualifyId(b.component, namespacePrefix, this.rootSubsystems)
|
|
21862
|
+
})),
|
|
21863
|
+
// A listener's mount names a portal id, qualified like any other
|
|
21864
|
+
// component reference so a chained listener still finds its portals.
|
|
21865
|
+
mounts: comp.mounts?.map((m) => ({
|
|
21866
|
+
...m,
|
|
21867
|
+
portal: qualifyId(m.portal, namespacePrefix, this.rootSubsystems)
|
|
21708
21868
|
}))
|
|
21709
21869
|
}));
|
|
21710
21870
|
index.interfaces = index.interfaces.map((intf) => ({
|
|
@@ -23249,6 +23409,9 @@ var SpecWorkspace = class {
|
|
|
23249
23409
|
if (Array.isArray(out.dispatch)) {
|
|
23250
23410
|
out.dispatch = out.dispatch.map((b) => typeof b?.component === "string" ? { ...b, component: q(b.component) } : b);
|
|
23251
23411
|
}
|
|
23412
|
+
if (Array.isArray(out.mounts)) {
|
|
23413
|
+
out.mounts = out.mounts.map((m) => typeof m?.portal === "string" ? { ...m, portal: q(m.portal) } : m);
|
|
23414
|
+
}
|
|
23252
23415
|
break;
|
|
23253
23416
|
case "interface":
|
|
23254
23417
|
if (typeof out.component === "string") out.component = q(out.component);
|
|
@@ -26629,6 +26792,7 @@ function loadProjectConfig4() {
|
|
|
26629
26792
|
PatternRefSchema,
|
|
26630
26793
|
PortalAuthSchema,
|
|
26631
26794
|
PortalAuthSchemeSchema,
|
|
26795
|
+
PortalMountSchema,
|
|
26632
26796
|
PortalTypeSchema,
|
|
26633
26797
|
ProfileSelectionSubjectSchema,
|
|
26634
26798
|
ProjectConfigSchema,
|