@wairon/cli 5.0.2-dev.7 → 5.0.2-dev.9
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 +247 -29
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +242 -26
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.0.2-dev.
|
|
68
|
+
WAIRON_VERSION = "5.0.2-dev.9";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
71
71
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -703,7 +703,7 @@ var init_template = __esm({
|
|
|
703
703
|
});
|
|
704
704
|
|
|
705
705
|
// src/models/specs.ts
|
|
706
|
-
var import_zod6, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, MethodSignatureSchema, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, GroupSpecSchema;
|
|
706
|
+
var import_zod6, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, MethodSignatureSchema, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
707
707
|
var init_specs = __esm({
|
|
708
708
|
"src/models/specs.ts"() {
|
|
709
709
|
"use strict";
|
|
@@ -914,6 +914,21 @@ var init_specs = __esm({
|
|
|
914
914
|
type: ExternalLinkTypeSchema.default("informative"),
|
|
915
915
|
label: import_zod6.z.string().optional()
|
|
916
916
|
});
|
|
917
|
+
PortalAuthSchemeSchema = import_zod6.z.enum(["none", "apiKey", "bearer", "basic", "oauth2", "openIdConnect", "custom"]);
|
|
918
|
+
PortalAuthSchema = import_zod6.z.object({
|
|
919
|
+
scheme: PortalAuthSchemeSchema,
|
|
920
|
+
in: import_zod6.z.enum(["header", "query", "cookie"]).optional(),
|
|
921
|
+
name: import_zod6.z.string().optional(),
|
|
922
|
+
bearerFormat: import_zod6.z.string().optional(),
|
|
923
|
+
authorizationUrl: import_zod6.z.string().optional(),
|
|
924
|
+
tokenUrl: import_zod6.z.string().optional(),
|
|
925
|
+
refreshUrl: import_zod6.z.string().optional(),
|
|
926
|
+
scopes: import_zod6.z.array(import_zod6.z.object({ name: import_zod6.z.string(), description: import_zod6.z.string() })).optional(),
|
|
927
|
+
flow: import_zod6.z.enum(["authorizationCode", "clientCredentials", "implicit", "password"]).optional(),
|
|
928
|
+
openIdConnectUrl: import_zod6.z.string().optional(),
|
|
929
|
+
description: import_zod6.z.string().optional(),
|
|
930
|
+
example: import_zod6.z.string().optional()
|
|
931
|
+
});
|
|
917
932
|
ComponentSpecSchema = import_zod6.z.object({
|
|
918
933
|
id: SpecIdSchema,
|
|
919
934
|
name: import_zod6.z.string(),
|
|
@@ -927,6 +942,10 @@ var init_specs = __esm({
|
|
|
927
942
|
dependsOn: import_zod6.z.array(import_zod6.z.string()).default([]),
|
|
928
943
|
portalType: PortalTypeSchema.optional(),
|
|
929
944
|
basePath: import_zod6.z.string().optional(),
|
|
945
|
+
/** Portal-only: the API's authentication scheme (see PortalAuthSchema) — projected
|
|
946
|
+
* into the generated OpenAPI's securitySchemes/security. Portals with different auth
|
|
947
|
+
* must be separate components (one auth per portal ⇒ one OpenAPI spec per portal). */
|
|
948
|
+
auth: PortalAuthSchema.optional(),
|
|
930
949
|
/** Portal-only: capability → component.method dispatch table (see DispatchBindingSchema). */
|
|
931
950
|
dispatch: import_zod6.z.array(DispatchBindingSchema).optional(),
|
|
932
951
|
/** Store-only: whether held state survives restart (see DurabilitySchema). */
|
|
@@ -1076,6 +1095,14 @@ var init_specs = __esm({
|
|
|
1076
1095
|
// Required if type is 'call', references Method name on target interface
|
|
1077
1096
|
capability: import_zod6.z.string().optional(),
|
|
1078
1097
|
// Required if type is 'dispatch': the capability routed through the target Portal's dispatch table
|
|
1098
|
+
/**
|
|
1099
|
+
* call/dispatch only: the credential this step presents to an authed callee
|
|
1100
|
+
* Portal, and WHERE it is loaded from (`from` — a secret-store component id,
|
|
1101
|
+
* `env:API_KEY`, a config key, a vault ref, …). A declared DESIGN NOTE — wairon
|
|
1102
|
+
* never fetches it — but its absence on a call into a Portal whose `auth ≠ none`
|
|
1103
|
+
* warns (PORTAL_AUTH_UNMET), so credential loading is never overlooked.
|
|
1104
|
+
*/
|
|
1105
|
+
auth: import_zod6.z.object({ from: import_zod6.z.string(), note: import_zod6.z.string().optional() }).optional(),
|
|
1079
1106
|
assertsGuarantees: import_zod6.z.array(GuaranteeSchema).optional(),
|
|
1080
1107
|
/**
|
|
1081
1108
|
* Declared entity invariants this step upholds, as "<type-id>.<invariant-id>"
|
|
@@ -1292,7 +1319,12 @@ var init_specs = __esm({
|
|
|
1292
1319
|
dispatch: import_zod6.z.array(DispatchBindingSchema).optional(),
|
|
1293
1320
|
details: import_zod6.z.string().default(""),
|
|
1294
1321
|
version: import_zod6.z.string().optional(),
|
|
1295
|
-
stability: import_zod6.z.string().optional()
|
|
1322
|
+
stability: import_zod6.z.string().optional(),
|
|
1323
|
+
/** Projected copy of the backing Portal's auth (see PortalAuthSchema) — the codec
|
|
1324
|
+
* emits it as OpenAPI securitySchemes/security. */
|
|
1325
|
+
auth: PortalAuthSchema.optional(),
|
|
1326
|
+
/** The backing Portal's basePath — becomes the per-portal OpenAPI `servers` url. */
|
|
1327
|
+
basePath: import_zod6.z.string().optional()
|
|
1296
1328
|
});
|
|
1297
1329
|
SurfaceSnapshotSchema = import_zod6.z.object({
|
|
1298
1330
|
/** Producing project/system name — the snapshot's resolution identity. */
|
|
@@ -1307,6 +1339,11 @@ var init_specs = __esm({
|
|
|
1307
1339
|
/** Transitive type closure of every exported signature — self-contained. */
|
|
1308
1340
|
types: import_zod6.z.array(SurfaceTypeDefSchema).default([])
|
|
1309
1341
|
});
|
|
1342
|
+
NamedOpenApiSpecSchema = import_zod6.z.object({
|
|
1343
|
+
portalId: import_zod6.z.string(),
|
|
1344
|
+
name: import_zod6.z.string(),
|
|
1345
|
+
document: import_zod6.z.string()
|
|
1346
|
+
});
|
|
1310
1347
|
GroupSpecSchema = import_zod6.z.object({
|
|
1311
1348
|
kind: import_zod6.z.literal("group"),
|
|
1312
1349
|
id: SpecIdSchema,
|
|
@@ -6115,7 +6152,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
6115
6152
|
+ (scopeFocus ? staticChip('current view') : '')
|
|
6116
6153
|
+ chip(c.subsystem, 'subsystem', c.subsystem)
|
|
6117
6154
|
+ (scopeFocus ? '' : openViewButton('component', c.id, c.owns.length > 0))
|
|
6118
|
-
+ openApiButton(componentExposesApi(c), c.
|
|
6155
|
+
+ openApiButton(componentExposesApi(c), c.id)
|
|
6119
6156
|
+ openSpecButton('component', c.id);
|
|
6120
6157
|
|
|
6121
6158
|
var linkedTypes = MODEL.types.filter(function (t) { return t.componentClass === c.id; });
|
|
@@ -6920,11 +6957,63 @@ function operationFor(method2, closureIds) {
|
|
|
6920
6957
|
}
|
|
6921
6958
|
return op;
|
|
6922
6959
|
}
|
|
6923
|
-
function
|
|
6924
|
-
|
|
6925
|
-
const
|
|
6960
|
+
function securitySchemeObject(auth) {
|
|
6961
|
+
if (auth.scheme === "none") return null;
|
|
6962
|
+
const desc = auth.description ? { description: auth.description } : {};
|
|
6963
|
+
switch (auth.scheme) {
|
|
6964
|
+
case "apiKey":
|
|
6965
|
+
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "X-API-Key", ...desc };
|
|
6966
|
+
case "bearer":
|
|
6967
|
+
return { type: "http", scheme: "bearer", ...auth.bearerFormat ? { bearerFormat: auth.bearerFormat } : {}, ...desc };
|
|
6968
|
+
case "basic":
|
|
6969
|
+
return { type: "http", scheme: "basic", ...desc };
|
|
6970
|
+
case "oauth2": {
|
|
6971
|
+
const flow = { scopes: Object.fromEntries((auth.scopes ?? []).map((s) => [s.name, s.description])) };
|
|
6972
|
+
if (auth.authorizationUrl) flow.authorizationUrl = auth.authorizationUrl;
|
|
6973
|
+
if (auth.tokenUrl) flow.tokenUrl = auth.tokenUrl;
|
|
6974
|
+
if (auth.refreshUrl) flow.refreshUrl = auth.refreshUrl;
|
|
6975
|
+
return { type: "oauth2", flows: { [auth.flow ?? "authorizationCode"]: flow }, ...desc };
|
|
6976
|
+
}
|
|
6977
|
+
case "openIdConnect":
|
|
6978
|
+
return { type: "openIdConnect", openIdConnectUrl: auth.openIdConnectUrl ?? "", ...desc };
|
|
6979
|
+
case "custom":
|
|
6980
|
+
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "Authorization", description: auth.description ?? auth.example ?? "Custom authentication scheme." };
|
|
6981
|
+
default:
|
|
6982
|
+
return null;
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
function schemeBaseName(scheme) {
|
|
6986
|
+
return { apiKey: "ApiKeyAuth", bearer: "BearerAuth", basic: "BasicAuth", oauth2: "OAuth2", openIdConnect: "OpenIdConnect", custom: "CustomAuth" }[scheme] ?? "Auth";
|
|
6987
|
+
}
|
|
6988
|
+
function buildSecurity(entries) {
|
|
6989
|
+
const schemes = {};
|
|
6990
|
+
const nameByContent = /* @__PURE__ */ new Map();
|
|
6991
|
+
const securityByEntry = /* @__PURE__ */ new Map();
|
|
6992
|
+
for (const entry of entries) {
|
|
6993
|
+
if (!entry.auth) continue;
|
|
6994
|
+
const obj = securitySchemeObject(entry.auth);
|
|
6995
|
+
if (!obj) continue;
|
|
6996
|
+
const content = JSON.stringify(obj);
|
|
6997
|
+
let name = nameByContent.get(content);
|
|
6998
|
+
if (!name) {
|
|
6999
|
+
name = schemeBaseName(entry.auth.scheme);
|
|
7000
|
+
for (let n = 2; schemes[name]; n++) name = schemeBaseName(entry.auth.scheme) + n;
|
|
7001
|
+
schemes[name] = obj;
|
|
7002
|
+
nameByContent.set(content, name);
|
|
7003
|
+
}
|
|
7004
|
+
const scopeNames = entry.auth.scheme === "oauth2" ? (entry.auth.scopes ?? []).map((s) => s.name) : [];
|
|
7005
|
+
securityByEntry.set(entry.id, { [name]: scopeNames });
|
|
7006
|
+
}
|
|
7007
|
+
return { schemes, securityByEntry };
|
|
7008
|
+
}
|
|
7009
|
+
function httpEntriesOf(snapshot) {
|
|
7010
|
+
return snapshot.interfaces.filter((e) => e.type === "REST" || e.methods.some((m) => m.endpoint?.transport === "HTTP"));
|
|
7011
|
+
}
|
|
7012
|
+
function renderDoc(snapshot, entries, closureIds, opts = {}) {
|
|
7013
|
+
const { schemes, securityByEntry } = buildSecurity(entries);
|
|
6926
7014
|
const paths = {};
|
|
6927
|
-
for (const entry of
|
|
7015
|
+
for (const entry of entries) {
|
|
7016
|
+
const security = securityByEntry.get(entry.id);
|
|
6928
7017
|
for (const method2 of entry.methods) {
|
|
6929
7018
|
const endpoint = method2.endpoint;
|
|
6930
7019
|
if (!endpoint || endpoint.transport !== "HTTP") continue;
|
|
@@ -6932,7 +7021,8 @@ function toOpenApi(snapshot) {
|
|
|
6932
7021
|
paths[p] = paths[p] ?? {};
|
|
6933
7022
|
paths[p][endpoint.method.toLowerCase()] = {
|
|
6934
7023
|
tags: [entry.id],
|
|
6935
|
-
...operationFor(method2, closureIds)
|
|
7024
|
+
...operationFor(method2, closureIds),
|
|
7025
|
+
...security ? { security: [security] } : {}
|
|
6936
7026
|
};
|
|
6937
7027
|
}
|
|
6938
7028
|
}
|
|
@@ -6945,19 +7035,41 @@ function toOpenApi(snapshot) {
|
|
|
6945
7035
|
required: t.fields.filter((f) => !f.optional).map((f) => f.name)
|
|
6946
7036
|
};
|
|
6947
7037
|
}
|
|
6948
|
-
const
|
|
7038
|
+
const components = {};
|
|
7039
|
+
if (Object.keys(schemas).length) components.schemas = schemas;
|
|
7040
|
+
if (Object.keys(schemes).length) components.securitySchemes = schemes;
|
|
7041
|
+
const basePaths = [...new Set(entries.map((e) => e.basePath).filter((b) => !!b))];
|
|
7042
|
+
const servers = opts.servers && basePaths.length === 1 ? [{ url: basePaths[0] }] : void 0;
|
|
7043
|
+
return {
|
|
6949
7044
|
openapi: "3.1.0",
|
|
6950
7045
|
info: {
|
|
6951
|
-
title: snapshot.projectName,
|
|
7046
|
+
title: opts.title ?? snapshot.projectName,
|
|
6952
7047
|
version: snapshot.version ?? "0.0.0",
|
|
6953
7048
|
...snapshot.stateId ? { "x-wairon-state-id": snapshot.stateId } : {},
|
|
6954
7049
|
"x-wairon-origin": snapshot.origin,
|
|
6955
7050
|
"x-wairon-generated-at": snapshot.generatedAt
|
|
6956
7051
|
},
|
|
7052
|
+
...servers ? { servers } : {},
|
|
6957
7053
|
paths,
|
|
6958
|
-
...Object.keys(
|
|
7054
|
+
...Object.keys(components).length ? { components } : {}
|
|
6959
7055
|
};
|
|
6960
|
-
|
|
7056
|
+
}
|
|
7057
|
+
function toOpenApiSet(snapshot) {
|
|
7058
|
+
const closureIds = new Set(snapshot.types.map((t) => t.id));
|
|
7059
|
+
const byPortal = /* @__PURE__ */ new Map();
|
|
7060
|
+
const order = [];
|
|
7061
|
+
for (const entry of httpEntriesOf(snapshot)) {
|
|
7062
|
+
if (!byPortal.has(entry.component)) {
|
|
7063
|
+
byPortal.set(entry.component, []);
|
|
7064
|
+
order.push(entry.component);
|
|
7065
|
+
}
|
|
7066
|
+
byPortal.get(entry.component).push(entry);
|
|
7067
|
+
}
|
|
7068
|
+
return order.map((portalId) => {
|
|
7069
|
+
const entries = byPortal.get(portalId);
|
|
7070
|
+
const name = entries[0]?.name ?? portalId;
|
|
7071
|
+
return { portalId, name, document: JSON.stringify(renderDoc(snapshot, entries, closureIds, { title: name, servers: true }), null, 2) };
|
|
7072
|
+
});
|
|
6961
7073
|
}
|
|
6962
7074
|
function isOpenApiDocument(body) {
|
|
6963
7075
|
try {
|
|
@@ -6979,6 +7091,39 @@ function typeRefFromSchema(schema) {
|
|
|
6979
7091
|
if (typeof t === "string" && t !== "object") return t;
|
|
6980
7092
|
return "json";
|
|
6981
7093
|
}
|
|
7094
|
+
function authFromSecurityScheme(scheme) {
|
|
7095
|
+
const desc = typeof scheme.description === "string" ? { description: scheme.description } : {};
|
|
7096
|
+
if (scheme.type === "apiKey") {
|
|
7097
|
+
return {
|
|
7098
|
+
scheme: "apiKey",
|
|
7099
|
+
...scheme.in === "header" || scheme.in === "query" || scheme.in === "cookie" ? { in: scheme.in } : {},
|
|
7100
|
+
...typeof scheme.name === "string" ? { name: scheme.name } : {},
|
|
7101
|
+
...desc
|
|
7102
|
+
};
|
|
7103
|
+
}
|
|
7104
|
+
if (scheme.type === "http") {
|
|
7105
|
+
if (scheme.scheme === "bearer") return { scheme: "bearer", ...typeof scheme.bearerFormat === "string" ? { bearerFormat: scheme.bearerFormat } : {}, ...desc };
|
|
7106
|
+
if (scheme.scheme === "basic") return { scheme: "basic", ...desc };
|
|
7107
|
+
}
|
|
7108
|
+
if (scheme.type === "oauth2") {
|
|
7109
|
+
const flows = scheme.flows ?? {};
|
|
7110
|
+
const flowKey = Object.keys(flows)[0];
|
|
7111
|
+
const f = flows[flowKey] ?? {};
|
|
7112
|
+
return {
|
|
7113
|
+
scheme: "oauth2",
|
|
7114
|
+
...flowKey === "authorizationCode" || flowKey === "clientCredentials" || flowKey === "implicit" || flowKey === "password" ? { flow: flowKey } : {},
|
|
7115
|
+
...typeof f.authorizationUrl === "string" ? { authorizationUrl: f.authorizationUrl } : {},
|
|
7116
|
+
...typeof f.tokenUrl === "string" ? { tokenUrl: f.tokenUrl } : {},
|
|
7117
|
+
...typeof f.refreshUrl === "string" ? { refreshUrl: f.refreshUrl } : {},
|
|
7118
|
+
scopes: Object.entries(f.scopes ?? {}).map(([name, description]) => ({ name, description })),
|
|
7119
|
+
...desc
|
|
7120
|
+
};
|
|
7121
|
+
}
|
|
7122
|
+
if (scheme.type === "openIdConnect") {
|
|
7123
|
+
return { scheme: "openIdConnect", openIdConnectUrl: typeof scheme.openIdConnectUrl === "string" ? scheme.openIdConnectUrl : "", ...desc };
|
|
7124
|
+
}
|
|
7125
|
+
return void 0;
|
|
7126
|
+
}
|
|
6982
7127
|
function fromOpenApi(document, projectName) {
|
|
6983
7128
|
let parsed;
|
|
6984
7129
|
try {
|
|
@@ -7056,6 +7201,9 @@ function fromOpenApi(document, projectName) {
|
|
|
7056
7201
|
}))
|
|
7057
7202
|
});
|
|
7058
7203
|
}
|
|
7204
|
+
const securitySchemes = parsed.components?.securitySchemes ?? {};
|
|
7205
|
+
const firstScheme = Object.values(securitySchemes)[0];
|
|
7206
|
+
const importedAuth = firstScheme ? authFromSecurityScheme(firstScheme) : void 0;
|
|
7059
7207
|
const entry = {
|
|
7060
7208
|
id: `${projectName}-api`,
|
|
7061
7209
|
name: typeof info.title === "string" ? info.title : projectName,
|
|
@@ -7064,7 +7212,8 @@ function fromOpenApi(document, projectName) {
|
|
|
7064
7212
|
component: `${projectName}-api`,
|
|
7065
7213
|
methods,
|
|
7066
7214
|
details: typeof info.description === "string" ? info.description : `Imported OpenAPI surface of ${projectName}.`,
|
|
7067
|
-
...typeof info.version === "string" ? { version: info.version } : {}
|
|
7215
|
+
...typeof info.version === "string" ? { version: info.version } : {},
|
|
7216
|
+
...importedAuth ? { auth: importedAuth } : {}
|
|
7068
7217
|
};
|
|
7069
7218
|
return SurfaceSnapshotSchema.parse({
|
|
7070
7219
|
projectName,
|
|
@@ -7179,6 +7328,10 @@ function projectOwnSurface(maxAudience) {
|
|
|
7179
7328
|
component: comp.id,
|
|
7180
7329
|
methods,
|
|
7181
7330
|
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
7331
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
7332
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
7333
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
7334
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
7182
7335
|
details: raw.details ?? "",
|
|
7183
7336
|
...raw.version ? { version: raw.version } : {},
|
|
7184
7337
|
...raw.stability ? { stability: raw.stability } : {}
|
|
@@ -7222,20 +7375,28 @@ function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
|
7222
7375
|
function loadSurfaceSnapshots() {
|
|
7223
7376
|
return listSnapshots();
|
|
7224
7377
|
}
|
|
7378
|
+
function renderOpenApiForms(snapshot) {
|
|
7379
|
+
const renderedSet = toOpenApiSet(snapshot);
|
|
7380
|
+
return { renderedSet, ...renderedSet.length === 1 ? { rendered: renderedSet[0].document } : {} };
|
|
7381
|
+
}
|
|
7382
|
+
function writeSurfaceFile(outPath, body, snapshot) {
|
|
7383
|
+
const resolved = path10.resolve(outPath);
|
|
7384
|
+
fs9.mkdirSync(path10.dirname(resolved), { recursive: true });
|
|
7385
|
+
if (body !== void 0) fs9.writeFileSync(resolved, body);
|
|
7386
|
+
else writeYamlFile(resolved, snapshot);
|
|
7387
|
+
return resolved;
|
|
7388
|
+
}
|
|
7225
7389
|
function exportSurface(maxAudience, format, outPath) {
|
|
7226
7390
|
const snapshot = projectOwnSurface(maxAudience);
|
|
7227
|
-
const
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
}
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
writtenTo = path10.resolve(outPath);
|
|
7237
|
-
}
|
|
7238
|
-
return { snapshot, ...rendered !== void 0 ? { rendered } : {}, ...writtenTo ? { writtenTo } : {} };
|
|
7391
|
+
const openapi = format === "openapi" ? renderOpenApiForms(snapshot) : void 0;
|
|
7392
|
+
const body = openapi?.rendered ?? openapi?.renderedSet[0]?.document;
|
|
7393
|
+
const writtenTo = outPath ? writeSurfaceFile(outPath, body, snapshot) : void 0;
|
|
7394
|
+
return {
|
|
7395
|
+
snapshot,
|
|
7396
|
+
...openapi?.rendered !== void 0 ? { rendered: openapi.rendered } : {},
|
|
7397
|
+
...openapi ? { renderedSet: openapi.renderedSet } : {},
|
|
7398
|
+
...writtenTo ? { writtenTo } : {}
|
|
7399
|
+
};
|
|
7239
7400
|
}
|
|
7240
7401
|
function importSurface(sourcePath, origin) {
|
|
7241
7402
|
const resolved = path10.resolve(sourcePath);
|
|
@@ -12034,6 +12195,44 @@ var init_lint_allows = __esm({
|
|
|
12034
12195
|
}
|
|
12035
12196
|
});
|
|
12036
12197
|
|
|
12198
|
+
// src/core/rules/portal-call-auth.ts
|
|
12199
|
+
var portalCallAuthRule;
|
|
12200
|
+
var init_portal_call_auth = __esm({
|
|
12201
|
+
"src/core/rules/portal-call-auth.ts"() {
|
|
12202
|
+
"use strict";
|
|
12203
|
+
portalCallAuthRule = {
|
|
12204
|
+
name: "portal-call-auth",
|
|
12205
|
+
description: "An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must declare the credential source it presents \u2014 the step's `auth.from`, a design note naming where the secret loads from (a secret-store component, env var, config key, vault ref). Its absence warns (PORTAL_AUTH_UNMET) so credential loading is never overlooked; 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.",
|
|
12206
|
+
codes: [
|
|
12207
|
+
{ code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" }
|
|
12208
|
+
],
|
|
12209
|
+
check(ctx) {
|
|
12210
|
+
for (const impl of ctx.implementations) {
|
|
12211
|
+
const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
|
|
12212
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
12213
|
+
for (const method2 of impl.methods ?? []) {
|
|
12214
|
+
for (const step of method2.narrative ?? []) {
|
|
12215
|
+
if (step.type !== "call") continue;
|
|
12216
|
+
if (!step.targetComponent || step.targetComponent === ownComponent) continue;
|
|
12217
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
12218
|
+
if (!target || target.componentType !== "Portal") continue;
|
|
12219
|
+
if (!target.auth || target.auth.scheme === "none") continue;
|
|
12220
|
+
if (step.auth?.from) continue;
|
|
12221
|
+
ctx.addIssue(
|
|
12222
|
+
"warning",
|
|
12223
|
+
"PORTAL_AUTH_UNMET",
|
|
12224
|
+
`Narrative step ${step.stepNumber} of "${method2.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.`,
|
|
12225
|
+
impl.id,
|
|
12226
|
+
draft
|
|
12227
|
+
);
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12230
|
+
}
|
|
12231
|
+
}
|
|
12232
|
+
};
|
|
12233
|
+
}
|
|
12234
|
+
});
|
|
12235
|
+
|
|
12037
12236
|
// src/core/rules/index.ts
|
|
12038
12237
|
function makeScopeFilter(opts) {
|
|
12039
12238
|
const { components, interfaces, implementations, types, scopeSubsystem } = opts;
|
|
@@ -12279,6 +12478,7 @@ var init_rules = __esm({
|
|
|
12279
12478
|
init_hidden_state();
|
|
12280
12479
|
init_dependency_conformance();
|
|
12281
12480
|
init_lint_allows();
|
|
12481
|
+
init_portal_call_auth();
|
|
12282
12482
|
init_source_analysis();
|
|
12283
12483
|
init_types();
|
|
12284
12484
|
init_type_analysis();
|
|
@@ -12300,6 +12500,9 @@ var init_rules = __esm({
|
|
|
12300
12500
|
narrativeAntipatternsRule,
|
|
12301
12501
|
narrativeDetailRule,
|
|
12302
12502
|
portalsRule,
|
|
12503
|
+
// Cross-call auth: a narrative call into an authed Portal must name its
|
|
12504
|
+
// credential source (rides with the portal family).
|
|
12505
|
+
portalCallAuthRule,
|
|
12303
12506
|
stereotypeDepsRule,
|
|
12304
12507
|
patternsRule,
|
|
12305
12508
|
// Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
|
|
@@ -12362,6 +12565,7 @@ var init_rules = __esm({
|
|
|
12362
12565
|
// L4 expectations: implementations and their code linkage.
|
|
12363
12566
|
MISSING_IMPLEMENTATION_METHOD: "implementations",
|
|
12364
12567
|
MISSING_SOURCE_PATH: "implementations",
|
|
12568
|
+
PORTAL_AUTH_UNMET: "implementations",
|
|
12365
12569
|
MISSING_SOURCE_FILE: "implementations",
|
|
12366
12570
|
SOURCE_PATH_ESCAPES_ROOT: "implementations",
|
|
12367
12571
|
UNREALIZED_METHOD: "implementations",
|
|
@@ -16925,6 +17129,7 @@ NOTICE:
|
|
|
16925
17129
|
type: import_zod9.z.enum(["local", "call", "dispatch", "branch", "switch", "loop", "try", "parallel", "jump", "return", "throw"]),
|
|
16926
17130
|
targetComponent: import_zod9.z.string().optional().describe("call/dispatch: L2 component id (for dispatch, the Portal routed through)"),
|
|
16927
17131
|
targetMethod: import_zod9.z.string().optional().describe("call: method name on the target"),
|
|
17132
|
+
auth: import_zod9.z.object({ from: import_zod9.z.string(), note: import_zod9.z.string().optional() }).optional().describe("call/dispatch: the credential this step presents to an AUTHED callee Portal and WHERE it loads from (`from` = a secret-store component id, env:API_KEY, a config key, a vault ref). A design note \u2014 wairon never fetches it. Its absence on a call into a Portal whose auth \u2260 none warns (PORTAL_AUTH_UNMET), so credential loading is never overlooked."),
|
|
16928
17133
|
detach: import_zod9.z.boolean().optional().describe("call/dispatch: fire-and-forget \u2014 issue the call and continue without awaiting the result (no later step consumes it)"),
|
|
16929
17134
|
capability: import_zod9.z.string().optional().describe("dispatch: the capability routed through the target Portal's dispatch table (validated against it \u2014 UNSERVED_CAPABILITY)"),
|
|
16930
17135
|
assertsGuarantees: import_zod9.z.array(import_zod9.z.string().min(1)).optional().describe("Semantic guarantees this step relies on \u2014 each must be declared in the called method's L3 guarantees (NARRATIVE_SEMANTIC_UNBACKED otherwise). Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
@@ -32127,7 +32332,7 @@ function getWebProjectCanvasModel(cfg, sessionId, projectId) {
|
|
|
32127
32332
|
}
|
|
32128
32333
|
return runWithProjectRoot(root, () => hostCore.buildCanvasDataModel());
|
|
32129
32334
|
}
|
|
32130
|
-
function getWebProjectOpenApi(cfg, sessionId, projectId) {
|
|
32335
|
+
function getWebProjectOpenApi(cfg, sessionId, projectId, portalId) {
|
|
32131
32336
|
const principal = authenticateSession(cfg.dataDir, sessionId);
|
|
32132
32337
|
if (!principal.authenticated) throw new UnauthenticatedError();
|
|
32133
32338
|
if (!listProjects2(cfg, sessionId).some((r) => r.id === projectId)) {
|
|
@@ -32136,7 +32341,19 @@ function getWebProjectOpenApi(cfg, sessionId, projectId) {
|
|
|
32136
32341
|
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
32137
32342
|
if (!root) throw new ForbiddenError("project not authorized or unknown");
|
|
32138
32343
|
const result = runWithProjectRoot(root, () => hostSurfaces.exportBoundSurface("project", "openapi"));
|
|
32139
|
-
|
|
32344
|
+
const specs = result.renderedSet ?? (result.rendered ? [{ portalId: "", name: projectId, document: result.rendered }] : []);
|
|
32345
|
+
if (specs.length === 0) return swaggerUiPage("{}", projectId);
|
|
32346
|
+
if (portalId) {
|
|
32347
|
+
const sel = specs.find((s) => s.portalId === portalId) ?? specs[0];
|
|
32348
|
+
return swaggerUiPage(sel.document, sel.name);
|
|
32349
|
+
}
|
|
32350
|
+
if (specs.length === 1) return swaggerUiPage(specs[0].document, specs[0].name);
|
|
32351
|
+
return openApiIndexPage(projectId, specs);
|
|
32352
|
+
}
|
|
32353
|
+
function openApiIndexPage(projectId, specs) {
|
|
32354
|
+
const esc2 = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
32355
|
+
const items = specs.map((s) => `<li><a href="/web/openapi?projectId=${encodeURIComponent(projectId)}&spec=${encodeURIComponent(s.portalId)}">${esc2(s.name)}</a> <code>${esc2(s.portalId)}</code></li>`).join("");
|
|
32356
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${esc2(projectId)} \u2014 API specs</title><style>body{font-family:system-ui,sans-serif;max-width:680px;margin:48px auto;padding:0 20px;color:#e6e6e6;background:#161616}h1{font-size:20px}a{color:#6ea8fe;text-decoration:none}a:hover{text-decoration:underline}li{margin:10px 0}code{color:#8a94a6;font-size:12px;margin-left:8px}</style></head><body><h1>${esc2(projectId)} \u2014 API specs</h1><p>This project exposes ${specs.length} separate public APIs, each with its own OpenAPI document and auth:</p><ul>${items}</ul></body></html>`;
|
|
32140
32357
|
}
|
|
32141
32358
|
function reshapeLandscapeGraph(model, level) {
|
|
32142
32359
|
const unitNodeIds = new Set(model.nodes.filter((n) => n.nodeKind === "orgUnit").map((n) => n.id));
|
|
@@ -34498,7 +34715,8 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
|
|
|
34498
34715
|
}
|
|
34499
34716
|
if (req.method === "GET" && parts.length === 2 && parts[1] === "openapi") {
|
|
34500
34717
|
const projectId = url.searchParams.get("projectId") ?? "";
|
|
34501
|
-
const
|
|
34718
|
+
const spec = url.searchParams.get("spec") ?? void 0;
|
|
34719
|
+
const html = getWebProjectOpenApi(cfg, sessionId, projectId, spec);
|
|
34502
34720
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
34503
34721
|
res.end(html);
|
|
34504
34722
|
return;
|