@wairon/cli 5.1.1-dev.68 → 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 +167 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +152 -3
- 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);
|
|
@@ -18486,6 +18614,9 @@ var SDD_RULES = [
|
|
|
18486
18614
|
dispatchTableBindingsRule,
|
|
18487
18615
|
dispatchStepRoutingRule,
|
|
18488
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,
|
|
18489
18620
|
// The declared entrypoint's own prose before the walk that its declaration
|
|
18490
18621
|
// silences, then the walk, then the types no walk can reach.
|
|
18491
18622
|
invokedByDescriptionRule,
|
|
@@ -21126,6 +21257,10 @@ function stripNamespaceFromComponent(spec, prefix) {
|
|
|
21126
21257
|
dispatch: spec.dispatch?.map((b) => ({
|
|
21127
21258
|
...b,
|
|
21128
21259
|
component: relativizeId(b.component, prefix)
|
|
21260
|
+
})),
|
|
21261
|
+
mounts: spec.mounts?.map((m) => ({
|
|
21262
|
+
...m,
|
|
21263
|
+
portal: relativizeId(m.portal, prefix)
|
|
21129
21264
|
}))
|
|
21130
21265
|
};
|
|
21131
21266
|
}
|
|
@@ -21276,6 +21411,7 @@ function rewriteSpecRefs(raw, remap) {
|
|
|
21276
21411
|
rewriteEach(raw.dependsOn, "component");
|
|
21277
21412
|
rewriteEach(raw.owns, "component");
|
|
21278
21413
|
for (const binding of entries(raw.dispatch)) rewritePair(binding, "component", "method");
|
|
21414
|
+
for (const mount of entries(raw.mounts)) rewrite(mount, "portal", "component");
|
|
21279
21415
|
} else if (kind === "subsystem") {
|
|
21280
21416
|
for (const entrypoint of entries(raw.lifecycle)) rewritePair(entrypoint, "component", "method");
|
|
21281
21417
|
rewritePublished(raw.publicInterfaces);
|
|
@@ -21330,6 +21466,9 @@ function identityKeyOf(field, item) {
|
|
|
21330
21466
|
switch (field) {
|
|
21331
21467
|
case "dispatch":
|
|
21332
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);
|
|
21333
21472
|
case "lifecycle":
|
|
21334
21473
|
return `${String(o.phase)} ${String(o.component)} ${String(o.method)}`;
|
|
21335
21474
|
case "emits":
|
|
@@ -21720,6 +21859,12 @@ var SpecWorkspace = class {
|
|
|
21720
21859
|
dispatch: comp.dispatch?.map((b) => ({
|
|
21721
21860
|
...b,
|
|
21722
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)
|
|
21723
21868
|
}))
|
|
21724
21869
|
}));
|
|
21725
21870
|
index.interfaces = index.interfaces.map((intf) => ({
|
|
@@ -23264,6 +23409,9 @@ var SpecWorkspace = class {
|
|
|
23264
23409
|
if (Array.isArray(out.dispatch)) {
|
|
23265
23410
|
out.dispatch = out.dispatch.map((b) => typeof b?.component === "string" ? { ...b, component: q(b.component) } : b);
|
|
23266
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
|
+
}
|
|
23267
23415
|
break;
|
|
23268
23416
|
case "interface":
|
|
23269
23417
|
if (typeof out.component === "string") out.component = q(out.component);
|
|
@@ -26644,6 +26792,7 @@ function loadProjectConfig4() {
|
|
|
26644
26792
|
PatternRefSchema,
|
|
26645
26793
|
PortalAuthSchema,
|
|
26646
26794
|
PortalAuthSchemeSchema,
|
|
26795
|
+
PortalMountSchema,
|
|
26647
26796
|
PortalTypeSchema,
|
|
26648
26797
|
ProfileSelectionSubjectSchema,
|
|
26649
26798
|
ProjectConfigSchema,
|