@wairon/cli 5.1.1-dev.10 → 5.1.1-dev.11
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 +1083 -991
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +2227 -2133
- package/dist/index.js.map +1 -1
- 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.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.11";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
71
71
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -186,11 +186,13 @@ __export(fs_exports, {
|
|
|
186
186
|
fromProjectRoot: () => fromProjectRoot,
|
|
187
187
|
getProjectRoot: () => getProjectRoot,
|
|
188
188
|
getProjectRootOverride: () => getProjectRootOverride,
|
|
189
|
+
getRequestParentReach: () => getRequestParentReach,
|
|
189
190
|
getRequestProjectRoot: () => getRequestProjectRoot,
|
|
190
191
|
listFiles: () => listFiles,
|
|
191
192
|
listFilesRecursive: () => listFilesRecursive,
|
|
192
193
|
pathExists: () => pathExists,
|
|
193
194
|
readFileOrNull: () => readFileOrNull,
|
|
195
|
+
runWithProjectBinding: () => runWithProjectBinding,
|
|
194
196
|
runWithProjectRoot: () => runWithProjectRoot,
|
|
195
197
|
setProjectRoot: () => setProjectRoot,
|
|
196
198
|
writeFile: () => writeFile,
|
|
@@ -241,10 +243,21 @@ function listFilesRecursive(dirPath, ext) {
|
|
|
241
243
|
return files;
|
|
242
244
|
}
|
|
243
245
|
function runWithProjectRoot(dir, fn) {
|
|
244
|
-
return requestRootStore.run(path4.resolve(dir), fn);
|
|
246
|
+
return requestRootStore.run({ ...requestRootStore.getStore(), root: path4.resolve(dir) }, fn);
|
|
247
|
+
}
|
|
248
|
+
function runWithProjectBinding(dir, reach, fn) {
|
|
249
|
+
return requestRootStore.run(
|
|
250
|
+
{ root: path4.resolve(dir), topRoot: path4.resolve(reach.topRoot), parentReach: reach.parentReach },
|
|
251
|
+
fn
|
|
252
|
+
);
|
|
245
253
|
}
|
|
246
254
|
function getRequestProjectRoot() {
|
|
247
|
-
return requestRootStore.getStore() ?? null;
|
|
255
|
+
return requestRootStore.getStore()?.root ?? null;
|
|
256
|
+
}
|
|
257
|
+
function getRequestParentReach() {
|
|
258
|
+
const scope = requestRootStore.getStore();
|
|
259
|
+
if (!scope || scope.parentReach === void 0) return null;
|
|
260
|
+
return { topRoot: scope.topRoot, parentReach: scope.parentReach };
|
|
248
261
|
}
|
|
249
262
|
function setProjectRoot(dir) {
|
|
250
263
|
projectRootOverride = dir === null ? null : path4.resolve(dir);
|
|
@@ -269,7 +282,7 @@ function findSystemRoot(startDir) {
|
|
|
269
282
|
}
|
|
270
283
|
}
|
|
271
284
|
function getProjectRoot() {
|
|
272
|
-
const scoped = requestRootStore.getStore();
|
|
285
|
+
const scoped = requestRootStore.getStore()?.root;
|
|
273
286
|
if (scoped) return scoped;
|
|
274
287
|
if (projectRootOverride) return projectRootOverride;
|
|
275
288
|
const systemRoot = findSystemRoot(process.cwd());
|
|
@@ -3534,733 +3547,6 @@ var init_type_references = __esm({
|
|
|
3534
3547
|
}
|
|
3535
3548
|
});
|
|
3536
3549
|
|
|
3537
|
-
// src/utils/filenames.ts
|
|
3538
|
-
function safeFilenamePart(value) {
|
|
3539
|
-
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
3540
|
-
}
|
|
3541
|
-
var init_filenames = __esm({
|
|
3542
|
-
"src/utils/filenames.ts"() {
|
|
3543
|
-
"use strict";
|
|
3544
|
-
}
|
|
3545
|
-
});
|
|
3546
|
-
|
|
3547
|
-
// src/core/openapi.ts
|
|
3548
|
-
function schemaFor(typeRef, closureIds) {
|
|
3549
|
-
const trimmed = typeRef.trim().replace(/^promise\s*<(.+)>$/i, "$1").trim();
|
|
3550
|
-
const arrayMatch = /^(.+)\[\]$/.exec(trimmed);
|
|
3551
|
-
if (arrayMatch) {
|
|
3552
|
-
return { type: "array", items: schemaFor(arrayMatch[1], closureIds) };
|
|
3553
|
-
}
|
|
3554
|
-
const lower = trimmed.toLowerCase();
|
|
3555
|
-
if (lower in PRIMITIVES) {
|
|
3556
|
-
const p = PRIMITIVES[lower];
|
|
3557
|
-
return p.type ? { ...p } : {};
|
|
3558
|
-
}
|
|
3559
|
-
const local = trimmed.split(/::|\./).pop().toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
3560
|
-
const hit = [...closureIds].find((id) => id.toLowerCase() === local);
|
|
3561
|
-
if (hit) return { $ref: `#/components/schemas/${hit}` };
|
|
3562
|
-
return { type: "object", description: `Unresolved type: ${trimmed}` };
|
|
3563
|
-
}
|
|
3564
|
-
function operationFor(method2, closureIds) {
|
|
3565
|
-
const endpoint = method2.endpoint;
|
|
3566
|
-
const httpVerb = endpoint && endpoint.transport === "HTTP" ? endpoint.method.toLowerCase() : "post";
|
|
3567
|
-
const bodyVerbs = /* @__PURE__ */ new Set(["post", "put", "patch"]);
|
|
3568
|
-
const params = method2.params ?? [];
|
|
3569
|
-
const op = {
|
|
3570
|
-
operationId: method2.name,
|
|
3571
|
-
summary: method2.description,
|
|
3572
|
-
responses: {
|
|
3573
|
-
"200": {
|
|
3574
|
-
description: method2.returns || "Success",
|
|
3575
|
-
...method2.returns && method2.returns.toLowerCase() !== "void" ? { content: { "application/json": { schema: schemaFor(method2.returns, closureIds) } } } : {}
|
|
3576
|
-
}
|
|
3577
|
-
}
|
|
3578
|
-
};
|
|
3579
|
-
if (method2.guarantees?.length) op["x-wairon-guarantees"] = method2.guarantees;
|
|
3580
|
-
if (method2.effect) op["x-wairon-effect"] = method2.effect;
|
|
3581
|
-
if (method2.ext && Object.keys(method2.ext).length) op["x-wairon-ext"] = method2.ext;
|
|
3582
|
-
if (params.length) {
|
|
3583
|
-
if (bodyVerbs.has(httpVerb)) {
|
|
3584
|
-
op.requestBody = {
|
|
3585
|
-
required: true,
|
|
3586
|
-
content: {
|
|
3587
|
-
"application/json": {
|
|
3588
|
-
schema: {
|
|
3589
|
-
type: "object",
|
|
3590
|
-
properties: Object.fromEntries(params.map((p) => [p.name, schemaFor(p.type, closureIds)])),
|
|
3591
|
-
required: params.filter((p) => !p.optional).map((p) => p.name)
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
}
|
|
3595
|
-
};
|
|
3596
|
-
} else {
|
|
3597
|
-
op.parameters = params.map((p) => ({
|
|
3598
|
-
name: p.name,
|
|
3599
|
-
in: "query",
|
|
3600
|
-
required: !p.optional,
|
|
3601
|
-
...p.description ? { description: p.description } : {},
|
|
3602
|
-
schema: schemaFor(p.type, closureIds)
|
|
3603
|
-
}));
|
|
3604
|
-
}
|
|
3605
|
-
}
|
|
3606
|
-
return op;
|
|
3607
|
-
}
|
|
3608
|
-
function securitySchemeObject(auth) {
|
|
3609
|
-
if (auth.scheme === "none") return null;
|
|
3610
|
-
const desc = auth.description ? { description: auth.description } : {};
|
|
3611
|
-
switch (auth.scheme) {
|
|
3612
|
-
case "apiKey":
|
|
3613
|
-
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "X-API-Key", ...desc };
|
|
3614
|
-
case "bearer":
|
|
3615
|
-
return { type: "http", scheme: "bearer", ...auth.bearerFormat ? { bearerFormat: auth.bearerFormat } : {}, ...desc };
|
|
3616
|
-
case "basic":
|
|
3617
|
-
return { type: "http", scheme: "basic", ...desc };
|
|
3618
|
-
case "oauth2": {
|
|
3619
|
-
const flow = { scopes: Object.fromEntries((auth.scopes ?? []).map((s) => [s.name, s.description])) };
|
|
3620
|
-
if (auth.authorizationUrl) flow.authorizationUrl = auth.authorizationUrl;
|
|
3621
|
-
if (auth.tokenUrl) flow.tokenUrl = auth.tokenUrl;
|
|
3622
|
-
if (auth.refreshUrl) flow.refreshUrl = auth.refreshUrl;
|
|
3623
|
-
return { type: "oauth2", flows: { [auth.flow ?? "authorizationCode"]: flow }, ...desc };
|
|
3624
|
-
}
|
|
3625
|
-
case "openIdConnect":
|
|
3626
|
-
return { type: "openIdConnect", openIdConnectUrl: auth.openIdConnectUrl ?? "", ...desc };
|
|
3627
|
-
case "custom":
|
|
3628
|
-
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "Authorization", description: auth.description ?? auth.example ?? "Custom authentication scheme." };
|
|
3629
|
-
default:
|
|
3630
|
-
return null;
|
|
3631
|
-
}
|
|
3632
|
-
}
|
|
3633
|
-
function schemeBaseName(scheme) {
|
|
3634
|
-
return { apiKey: "ApiKeyAuth", bearer: "BearerAuth", basic: "BasicAuth", oauth2: "OAuth2", openIdConnect: "OpenIdConnect", custom: "CustomAuth" }[scheme] ?? "Auth";
|
|
3635
|
-
}
|
|
3636
|
-
function buildSecurity(entries) {
|
|
3637
|
-
const schemes = {};
|
|
3638
|
-
const nameByContent = /* @__PURE__ */ new Map();
|
|
3639
|
-
const securityByEntry = /* @__PURE__ */ new Map();
|
|
3640
|
-
for (const entry of entries) {
|
|
3641
|
-
if (!entry.auth) continue;
|
|
3642
|
-
const obj = securitySchemeObject(entry.auth);
|
|
3643
|
-
if (!obj) continue;
|
|
3644
|
-
const content = JSON.stringify(obj);
|
|
3645
|
-
let name = nameByContent.get(content);
|
|
3646
|
-
if (!name) {
|
|
3647
|
-
name = schemeBaseName(entry.auth.scheme);
|
|
3648
|
-
for (let n = 2; schemes[name]; n++) name = schemeBaseName(entry.auth.scheme) + n;
|
|
3649
|
-
schemes[name] = obj;
|
|
3650
|
-
nameByContent.set(content, name);
|
|
3651
|
-
}
|
|
3652
|
-
const scopeNames = entry.auth.scheme === "oauth2" ? (entry.auth.scopes ?? []).map((s) => s.name) : [];
|
|
3653
|
-
securityByEntry.set(entry.id, { [name]: scopeNames });
|
|
3654
|
-
}
|
|
3655
|
-
return { schemes, securityByEntry };
|
|
3656
|
-
}
|
|
3657
|
-
function httpEntriesOf(snapshot) {
|
|
3658
|
-
return snapshot.interfaces.filter((e) => e.type === "REST" || e.methods.some((m) => m.endpoint?.transport === "HTTP"));
|
|
3659
|
-
}
|
|
3660
|
-
function renderDoc(snapshot, entries, closureIds, opts = {}) {
|
|
3661
|
-
const { schemes, securityByEntry } = buildSecurity(entries);
|
|
3662
|
-
const paths = {};
|
|
3663
|
-
for (const entry of entries) {
|
|
3664
|
-
const security = securityByEntry.get(entry.id);
|
|
3665
|
-
for (const method2 of entry.methods) {
|
|
3666
|
-
const endpoint = method2.endpoint;
|
|
3667
|
-
if (!endpoint || endpoint.transport !== "HTTP") continue;
|
|
3668
|
-
const p = endpoint.path.startsWith("/") ? endpoint.path : `/${endpoint.path}`;
|
|
3669
|
-
paths[p] = paths[p] ?? {};
|
|
3670
|
-
paths[p][endpoint.method.toLowerCase()] = {
|
|
3671
|
-
tags: [entry.id],
|
|
3672
|
-
...operationFor(method2, closureIds),
|
|
3673
|
-
...security ? { security: [security] } : {}
|
|
3674
|
-
};
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
const schemas = {};
|
|
3678
|
-
for (const t of snapshot.types) {
|
|
3679
|
-
schemas[t.id] = {
|
|
3680
|
-
type: "object",
|
|
3681
|
-
title: t.name,
|
|
3682
|
-
properties: Object.fromEntries(t.fields.map((f) => [f.name, schemaFor(f.type, closureIds)])),
|
|
3683
|
-
required: t.fields.filter((f) => !f.optional).map((f) => f.name)
|
|
3684
|
-
};
|
|
3685
|
-
}
|
|
3686
|
-
const components = {};
|
|
3687
|
-
if (Object.keys(schemas).length) components.schemas = schemas;
|
|
3688
|
-
if (Object.keys(schemes).length) components.securitySchemes = schemes;
|
|
3689
|
-
const basePaths = [...new Set(entries.map((e) => e.basePath).filter((b) => !!b))];
|
|
3690
|
-
const servers = opts.servers && basePaths.length === 1 ? [{ url: basePaths[0] }] : void 0;
|
|
3691
|
-
return {
|
|
3692
|
-
openapi: "3.1.0",
|
|
3693
|
-
info: {
|
|
3694
|
-
title: opts.title ?? snapshot.projectName,
|
|
3695
|
-
version: snapshot.version ?? "0.0.0",
|
|
3696
|
-
...snapshot.stateId ? { "x-wairon-state-id": snapshot.stateId } : {},
|
|
3697
|
-
"x-wairon-origin": snapshot.origin,
|
|
3698
|
-
"x-wairon-generated-at": snapshot.generatedAt
|
|
3699
|
-
},
|
|
3700
|
-
...servers ? { servers } : {},
|
|
3701
|
-
paths,
|
|
3702
|
-
...Object.keys(components).length ? { components } : {}
|
|
3703
|
-
};
|
|
3704
|
-
}
|
|
3705
|
-
function toOpenApiSet(snapshot) {
|
|
3706
|
-
const closureIds = new Set(snapshot.types.map((t) => t.id));
|
|
3707
|
-
const byPortal = /* @__PURE__ */ new Map();
|
|
3708
|
-
const order = [];
|
|
3709
|
-
for (const entry of httpEntriesOf(snapshot)) {
|
|
3710
|
-
if (!byPortal.has(entry.component)) {
|
|
3711
|
-
byPortal.set(entry.component, []);
|
|
3712
|
-
order.push(entry.component);
|
|
3713
|
-
}
|
|
3714
|
-
byPortal.get(entry.component).push(entry);
|
|
3715
|
-
}
|
|
3716
|
-
return order.map((portalId) => {
|
|
3717
|
-
const entries = byPortal.get(portalId);
|
|
3718
|
-
const name = entries[0]?.name ?? portalId;
|
|
3719
|
-
return { portalId, name, document: JSON.stringify(renderDoc(snapshot, entries, closureIds, { title: name, servers: true }), null, 2) };
|
|
3720
|
-
});
|
|
3721
|
-
}
|
|
3722
|
-
function isOpenApiDocument(body) {
|
|
3723
|
-
try {
|
|
3724
|
-
const parsed = yaml2.load(body);
|
|
3725
|
-
return !!parsed && typeof parsed === "object" && typeof parsed.openapi === "string";
|
|
3726
|
-
} catch {
|
|
3727
|
-
return false;
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
function typeRefFromSchema(schema) {
|
|
3731
|
-
if (!schema) return "json";
|
|
3732
|
-
const ref = schema.$ref;
|
|
3733
|
-
if (typeof ref === "string") return ref.split("/").pop() ?? "json";
|
|
3734
|
-
if (schema.type === "array") {
|
|
3735
|
-
return `${typeRefFromSchema(schema.items)}[]`;
|
|
3736
|
-
}
|
|
3737
|
-
const t = schema.type;
|
|
3738
|
-
if (t === "integer") return "int";
|
|
3739
|
-
if (typeof t === "string" && t !== "object") return t;
|
|
3740
|
-
return "json";
|
|
3741
|
-
}
|
|
3742
|
-
function authFromSecurityScheme(scheme) {
|
|
3743
|
-
const desc = typeof scheme.description === "string" ? { description: scheme.description } : {};
|
|
3744
|
-
if (scheme.type === "apiKey") {
|
|
3745
|
-
return {
|
|
3746
|
-
scheme: "apiKey",
|
|
3747
|
-
...scheme.in === "header" || scheme.in === "query" || scheme.in === "cookie" ? { in: scheme.in } : {},
|
|
3748
|
-
...typeof scheme.name === "string" ? { name: scheme.name } : {},
|
|
3749
|
-
...desc
|
|
3750
|
-
};
|
|
3751
|
-
}
|
|
3752
|
-
if (scheme.type === "http") {
|
|
3753
|
-
if (scheme.scheme === "bearer") return { scheme: "bearer", ...typeof scheme.bearerFormat === "string" ? { bearerFormat: scheme.bearerFormat } : {}, ...desc };
|
|
3754
|
-
if (scheme.scheme === "basic") return { scheme: "basic", ...desc };
|
|
3755
|
-
}
|
|
3756
|
-
if (scheme.type === "oauth2") {
|
|
3757
|
-
const flows = scheme.flows ?? {};
|
|
3758
|
-
const flowKey = Object.keys(flows)[0];
|
|
3759
|
-
const f = flows[flowKey] ?? {};
|
|
3760
|
-
return {
|
|
3761
|
-
scheme: "oauth2",
|
|
3762
|
-
...flowKey === "authorizationCode" || flowKey === "clientCredentials" || flowKey === "implicit" || flowKey === "password" ? { flow: flowKey } : {},
|
|
3763
|
-
...typeof f.authorizationUrl === "string" ? { authorizationUrl: f.authorizationUrl } : {},
|
|
3764
|
-
...typeof f.tokenUrl === "string" ? { tokenUrl: f.tokenUrl } : {},
|
|
3765
|
-
...typeof f.refreshUrl === "string" ? { refreshUrl: f.refreshUrl } : {},
|
|
3766
|
-
scopes: Object.entries(f.scopes ?? {}).map(([name, description]) => ({ name, description })),
|
|
3767
|
-
...desc
|
|
3768
|
-
};
|
|
3769
|
-
}
|
|
3770
|
-
if (scheme.type === "openIdConnect") {
|
|
3771
|
-
return { scheme: "openIdConnect", openIdConnectUrl: typeof scheme.openIdConnectUrl === "string" ? scheme.openIdConnectUrl : "", ...desc };
|
|
3772
|
-
}
|
|
3773
|
-
return void 0;
|
|
3774
|
-
}
|
|
3775
|
-
function fromOpenApi(document, projectName) {
|
|
3776
|
-
let parsed;
|
|
3777
|
-
try {
|
|
3778
|
-
parsed = yaml2.load(document);
|
|
3779
|
-
} catch (e) {
|
|
3780
|
-
throw new Error(`Invalid surface document: not parseable as JSON/YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
3781
|
-
}
|
|
3782
|
-
if (!parsed || typeof parsed !== "object" || typeof parsed.openapi !== "string" || typeof parsed.paths !== "object") {
|
|
3783
|
-
throw new Error('Invalid surface document: missing OpenAPI "openapi"/"paths" structure.');
|
|
3784
|
-
}
|
|
3785
|
-
const info = parsed.info ?? {};
|
|
3786
|
-
const methods = [];
|
|
3787
|
-
for (const [rawPath, ops] of Object.entries(parsed.paths)) {
|
|
3788
|
-
for (const [verb, opRaw] of Object.entries(ops ?? {})) {
|
|
3789
|
-
if (!["get", "post", "put", "delete", "patch", "options", "head"].includes(verb)) continue;
|
|
3790
|
-
const op = opRaw ?? {};
|
|
3791
|
-
const name = typeof op.operationId === "string" && /^[a-zA-Z0-9_]+$/.test(op.operationId) ? op.operationId : `${verb}_${rawPath.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
3792
|
-
const params = [];
|
|
3793
|
-
for (const p of op.parameters ?? []) {
|
|
3794
|
-
if (typeof p.name !== "string") continue;
|
|
3795
|
-
params.push({
|
|
3796
|
-
name: p.name,
|
|
3797
|
-
type: typeRefFromSchema(p.schema),
|
|
3798
|
-
...p.required === true ? {} : { optional: true },
|
|
3799
|
-
...typeof p.description === "string" ? { description: p.description } : {}
|
|
3800
|
-
});
|
|
3801
|
-
}
|
|
3802
|
-
const bodySchema = op.requestBody?.content?.["application/json"]?.schema;
|
|
3803
|
-
if (bodySchema) {
|
|
3804
|
-
const props = bodySchema.properties ?? {};
|
|
3805
|
-
const required = new Set(bodySchema.required ?? []);
|
|
3806
|
-
if (Object.keys(props).length) {
|
|
3807
|
-
for (const [pname, pschema] of Object.entries(props)) {
|
|
3808
|
-
params.push({ name: pname, type: typeRefFromSchema(pschema), ...required.has(pname) ? {} : { optional: true } });
|
|
3809
|
-
}
|
|
3810
|
-
} else {
|
|
3811
|
-
params.push({ name: "body", type: typeRefFromSchema(bodySchema) });
|
|
3812
|
-
}
|
|
3813
|
-
}
|
|
3814
|
-
const okResponse = op.responses?.["200"] ?? op.responses?.["201"];
|
|
3815
|
-
const responseSchema = okResponse?.content?.["application/json"]?.schema;
|
|
3816
|
-
const returns = responseSchema ? typeRefFromSchema(responseSchema) : "void";
|
|
3817
|
-
const rawGuarantees = op["x-wairon-guarantees"];
|
|
3818
|
-
const guarantees = Array.isArray(rawGuarantees) ? rawGuarantees.filter((g) => typeof g === "string" && g.length > 0) : [];
|
|
3819
|
-
const rawEffect = op["x-wairon-effect"];
|
|
3820
|
-
const effect = rawEffect === "read" || rawEffect === "write" ? rawEffect : void 0;
|
|
3821
|
-
const rawExt = op["x-wairon-ext"];
|
|
3822
|
-
const ext = rawExt && typeof rawExt === "object" && !Array.isArray(rawExt) ? rawExt : void 0;
|
|
3823
|
-
methods.push({
|
|
3824
|
-
name,
|
|
3825
|
-
description: typeof op.summary === "string" ? op.summary : typeof op.description === "string" ? op.description : name,
|
|
3826
|
-
signature: `${name}(${params.map((p) => `${p.name}: ${p.type}`).join(", ")}): ${returns}`,
|
|
3827
|
-
returns,
|
|
3828
|
-
params,
|
|
3829
|
-
endpoint: { transport: "HTTP", method: verb.toUpperCase(), path: rawPath },
|
|
3830
|
-
...guarantees.length ? { guarantees } : {},
|
|
3831
|
-
...effect ? { effect } : {},
|
|
3832
|
-
...ext ? { ext } : {}
|
|
3833
|
-
});
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
const types = [];
|
|
3837
|
-
const schemas = parsed.components?.schemas ?? {};
|
|
3838
|
-
for (const [id, schema] of Object.entries(schemas)) {
|
|
3839
|
-
const props = schema.properties ?? {};
|
|
3840
|
-
const required = new Set(schema.required ?? []);
|
|
3841
|
-
types.push({
|
|
3842
|
-
id,
|
|
3843
|
-
name: typeof schema.title === "string" ? schema.title : id,
|
|
3844
|
-
kind: "value-object",
|
|
3845
|
-
fields: Object.entries(props).map(([fname, fschema]) => ({
|
|
3846
|
-
name: fname,
|
|
3847
|
-
type: typeRefFromSchema(fschema),
|
|
3848
|
-
...required.has(fname) ? {} : { optional: true }
|
|
3849
|
-
}))
|
|
3850
|
-
});
|
|
3851
|
-
}
|
|
3852
|
-
const securitySchemes = parsed.components?.securitySchemes ?? {};
|
|
3853
|
-
const firstScheme = Object.values(securitySchemes)[0];
|
|
3854
|
-
const importedAuth = firstScheme ? authFromSecurityScheme(firstScheme) : void 0;
|
|
3855
|
-
const entry = {
|
|
3856
|
-
id: `${projectName}-api`,
|
|
3857
|
-
name: typeof info.title === "string" ? info.title : projectName,
|
|
3858
|
-
audience: "external",
|
|
3859
|
-
type: "REST",
|
|
3860
|
-
component: `${projectName}-api`,
|
|
3861
|
-
methods,
|
|
3862
|
-
details: typeof info.description === "string" ? info.description : `Imported OpenAPI surface of ${projectName}.`,
|
|
3863
|
-
...typeof info.version === "string" ? { version: info.version } : {},
|
|
3864
|
-
...importedAuth ? { auth: importedAuth } : {}
|
|
3865
|
-
};
|
|
3866
|
-
return SurfaceSnapshotSchema.parse({
|
|
3867
|
-
projectName,
|
|
3868
|
-
origin: "authored",
|
|
3869
|
-
...typeof info.version === "string" ? { version: info.version } : {},
|
|
3870
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3871
|
-
interfaces: [entry],
|
|
3872
|
-
types
|
|
3873
|
-
});
|
|
3874
|
-
}
|
|
3875
|
-
var yaml2, PRIMITIVES;
|
|
3876
|
-
var init_openapi = __esm({
|
|
3877
|
-
"src/core/openapi.ts"() {
|
|
3878
|
-
"use strict";
|
|
3879
|
-
yaml2 = __toESM(require("js-yaml"));
|
|
3880
|
-
init_models();
|
|
3881
|
-
PRIMITIVES = {
|
|
3882
|
-
string: { type: "string" },
|
|
3883
|
-
number: { type: "number" },
|
|
3884
|
-
float: { type: "number" },
|
|
3885
|
-
decimal: { type: "number" },
|
|
3886
|
-
int: { type: "integer" },
|
|
3887
|
-
integer: { type: "integer" },
|
|
3888
|
-
boolean: { type: "boolean" },
|
|
3889
|
-
bool: { type: "boolean" },
|
|
3890
|
-
date: { type: "string", format: "date-time" },
|
|
3891
|
-
datetime: { type: "string", format: "date-time" },
|
|
3892
|
-
uuid: { type: "string", format: "uuid" },
|
|
3893
|
-
json: { type: "object" },
|
|
3894
|
-
object: { type: "object" },
|
|
3895
|
-
any: {},
|
|
3896
|
-
unknown: {},
|
|
3897
|
-
void: {}
|
|
3898
|
-
};
|
|
3899
|
-
}
|
|
3900
|
-
});
|
|
3901
|
-
|
|
3902
|
-
// src/core/surfaces.ts
|
|
3903
|
-
function surfacesDir(rootDir) {
|
|
3904
|
-
return path11.join(rootDir, ".wai", SURFACES_DIRNAME);
|
|
3905
|
-
}
|
|
3906
|
-
function audienceRank(audience) {
|
|
3907
|
-
const idx = SURFACE_AUDIENCES.indexOf(audience ?? "instance");
|
|
3908
|
-
return idx === -1 ? SURFACE_AUDIENCES.indexOf("instance") : idx;
|
|
3909
|
-
}
|
|
3910
|
-
function stateIdString() {
|
|
3911
|
-
const s = computeStateId();
|
|
3912
|
-
return `${s.algorithm}:${s.digest}`;
|
|
3913
|
-
}
|
|
3914
|
-
function computeTypeClosure(entries, types) {
|
|
3915
|
-
const included = /* @__PURE__ */ new Map();
|
|
3916
|
-
const queue = [];
|
|
3917
|
-
const enqueueRef = (ref) => {
|
|
3918
|
-
if (BUILTIN_TYPES.has(ref.toLowerCase())) return;
|
|
3919
|
-
for (const spec of types) {
|
|
3920
|
-
const qualifiedId2 = spec.subsystem && !spec.id.startsWith(`${spec.subsystem}::`) ? `${spec.subsystem}::${spec.id}` : spec.id;
|
|
3921
|
-
if (matchTypeRef(ref, qualifiedId2) && !included.has(spec.id)) {
|
|
3922
|
-
included.set(spec.id, spec);
|
|
3923
|
-
queue.push(spec.id);
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
};
|
|
3927
|
-
for (const entry of entries) {
|
|
3928
|
-
for (const m of entry.methods) {
|
|
3929
|
-
for (const ref of methodTypeRefs(m)) enqueueRef(ref);
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
while (queue.length) {
|
|
3933
|
-
const spec = included.get(queue.shift());
|
|
3934
|
-
for (const field of spec.fields) {
|
|
3935
|
-
for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
3938
|
-
return [...included.values()].map((t) => ({
|
|
3939
|
-
id: t.id,
|
|
3940
|
-
name: t.name,
|
|
3941
|
-
kind: t.kind,
|
|
3942
|
-
fields: t.fields.map((f) => ({
|
|
3943
|
-
name: f.name,
|
|
3944
|
-
type: f.type,
|
|
3945
|
-
...f.description ? { description: f.description } : {},
|
|
3946
|
-
...f.optional ? { optional: true } : {}
|
|
3947
|
-
}))
|
|
3948
|
-
}));
|
|
3949
|
-
}
|
|
3950
|
-
function projectOwnSurface(maxAudience) {
|
|
3951
|
-
const system = loadSystemSpec();
|
|
3952
|
-
if (!system) {
|
|
3953
|
-
throw new Error("Cannot project a surface: the L0 system spec is missing.");
|
|
3954
|
-
}
|
|
3955
|
-
const subsystems = loadSubsystemSpecs();
|
|
3956
|
-
const components = loadComponentSpecs();
|
|
3957
|
-
const interfaces = loadInterfaceSpecs();
|
|
3958
|
-
const types = loadTypeSpecs();
|
|
3959
|
-
const floor = audienceRank(maxAudience);
|
|
3960
|
-
const rawEntries = system.publicInterfaces ?? [];
|
|
3961
|
-
const entries = [];
|
|
3962
|
-
for (const raw of rawEntries) {
|
|
3963
|
-
const audience = raw.audience ?? "instance";
|
|
3964
|
-
if (audienceRank(audience) < floor) continue;
|
|
3965
|
-
if (!raw.component) continue;
|
|
3966
|
-
const comp = components.find((c) => c.id === raw.component);
|
|
3967
|
-
if (!comp) continue;
|
|
3968
|
-
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!raw.interface || i.id === raw.interface));
|
|
3969
|
-
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
3970
|
-
const subsystemType = subsystems.find((s) => s.id === comp.subsystem)?.publicInterfaces.find((pi) => pi.component === comp.id)?.type;
|
|
3971
|
-
entries.push({
|
|
3972
|
-
id: raw.id ?? raw.interface ?? comp.id,
|
|
3973
|
-
name: raw.name ?? comp.name,
|
|
3974
|
-
audience,
|
|
3975
|
-
type: raw.type ?? subsystemType ?? "Custom",
|
|
3976
|
-
component: comp.id,
|
|
3977
|
-
methods,
|
|
3978
|
-
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
3979
|
-
// Project the backing Portal's auth + basePath so the codec can emit
|
|
3980
|
-
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
3981
|
-
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
3982
|
-
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
3983
|
-
details: raw.details ?? "",
|
|
3984
|
-
...raw.version ? { version: raw.version } : {},
|
|
3985
|
-
...raw.stability ? { stability: raw.stability } : {}
|
|
3986
|
-
});
|
|
3987
|
-
}
|
|
3988
|
-
return SurfaceSnapshotSchema.parse({
|
|
3989
|
-
projectName: system.name,
|
|
3990
|
-
origin: "generated",
|
|
3991
|
-
stateId: stateIdString(),
|
|
3992
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3993
|
-
interfaces: entries,
|
|
3994
|
-
types: computeTypeClosure(entries, types)
|
|
3995
|
-
});
|
|
3996
|
-
}
|
|
3997
|
-
function projectChildSurface() {
|
|
3998
|
-
return projectOwnSurface("project");
|
|
3999
|
-
}
|
|
4000
|
-
function localName(id) {
|
|
4001
|
-
return id.split("::").pop();
|
|
4002
|
-
}
|
|
4003
|
-
function projectSubsystemSurface(subsystemId) {
|
|
4004
|
-
const system = loadSystemSpec();
|
|
4005
|
-
if (!system) {
|
|
4006
|
-
throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
|
|
4007
|
-
}
|
|
4008
|
-
const subsystems = loadSubsystemSpecs();
|
|
4009
|
-
const target = subsystems.find((s) => s.id === subsystemId);
|
|
4010
|
-
if (!target) {
|
|
4011
|
-
throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
|
|
4012
|
-
}
|
|
4013
|
-
const components = loadComponentSpecs();
|
|
4014
|
-
const interfaces = loadInterfaceSpecs();
|
|
4015
|
-
const types = loadTypeSpecs();
|
|
4016
|
-
const entries = [];
|
|
4017
|
-
const unprojectable = [];
|
|
4018
|
-
for (const pub of target.publicInterfaces ?? []) {
|
|
4019
|
-
if (!pub.component) continue;
|
|
4020
|
-
const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
|
|
4021
|
-
if (!comp) continue;
|
|
4022
|
-
if (!CROSS_BOUNDARY_TARGETS.has(comp.componentType)) {
|
|
4023
|
-
unprojectable.push({ component: pub.component, componentType: comp.componentType });
|
|
4024
|
-
continue;
|
|
4025
|
-
}
|
|
4026
|
-
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
|
|
4027
|
-
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
4028
|
-
entries.push({
|
|
4029
|
-
id: localName(pub.interface ?? comp.id),
|
|
4030
|
-
name: comp.name,
|
|
4031
|
-
// Family ceiling: a sibling surface is consumable by the system family only.
|
|
4032
|
-
audience: "project",
|
|
4033
|
-
type: pub.type ?? "Custom",
|
|
4034
|
-
// The snapshot carries the LOCAL portal name — consumers resolve cross-tree
|
|
4035
|
-
// refs by their final segment.
|
|
4036
|
-
component: localName(comp.id),
|
|
4037
|
-
methods,
|
|
4038
|
-
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
4039
|
-
// Project the backing component's auth + basePath so the codec can emit
|
|
4040
|
-
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
4041
|
-
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
4042
|
-
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
4043
|
-
details: pub.details ?? ""
|
|
4044
|
-
});
|
|
4045
|
-
}
|
|
4046
|
-
for (const skipped of unprojectable) {
|
|
4047
|
-
console.error(
|
|
4048
|
-
`[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).`
|
|
4049
|
-
);
|
|
4050
|
-
}
|
|
4051
|
-
return SurfaceSnapshotSchema.parse({
|
|
4052
|
-
projectName: `${system.name}::${subsystemId}`,
|
|
4053
|
-
origin: "generated",
|
|
4054
|
-
stateId: stateIdString(),
|
|
4055
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4056
|
-
interfaces: entries,
|
|
4057
|
-
types: computeTypeClosure(entries, types)
|
|
4058
|
-
});
|
|
4059
|
-
}
|
|
4060
|
-
function listSnapshots(rootDir = getProjectRoot()) {
|
|
4061
|
-
const dir = surfacesDir(rootDir);
|
|
4062
|
-
if (!fs9.existsSync(dir)) return [];
|
|
4063
|
-
const out = [];
|
|
4064
|
-
for (const file of fs9.readdirSync(dir)) {
|
|
4065
|
-
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
|
|
4066
|
-
try {
|
|
4067
|
-
out.push(SurfaceSnapshotSchema.parse(readYamlFile(path11.join(dir, file))));
|
|
4068
|
-
} catch {
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
return out;
|
|
4072
|
-
}
|
|
4073
|
-
function getSnapshot(projectName, rootDir = getProjectRoot()) {
|
|
4074
|
-
return listSnapshots(rootDir).find((s) => s.projectName === projectName) ?? null;
|
|
4075
|
-
}
|
|
4076
|
-
function snapshotFilename(projectName) {
|
|
4077
|
-
return `${safeFilenamePart(projectName)}.yaml`;
|
|
4078
|
-
}
|
|
4079
|
-
function writeSnapshotIfChanged(snapshot, rootDir) {
|
|
4080
|
-
const dir = surfacesDir(rootDir);
|
|
4081
|
-
fs9.mkdirSync(dir, { recursive: true });
|
|
4082
|
-
const p = path11.join(dir, snapshotFilename(snapshot.projectName));
|
|
4083
|
-
const next = SurfaceSnapshotSchema.parse(snapshot);
|
|
4084
|
-
if (fs9.existsSync(p)) {
|
|
4085
|
-
try {
|
|
4086
|
-
const existing = SurfaceSnapshotSchema.parse(readYamlFile(p));
|
|
4087
|
-
if (surfaceContentKey(existing) === surfaceContentKey(next)) {
|
|
4088
|
-
return { path: p, changed: false };
|
|
4089
|
-
}
|
|
4090
|
-
} catch {
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4093
|
-
writeYamlFile(p, next);
|
|
4094
|
-
return { path: p, changed: true };
|
|
4095
|
-
}
|
|
4096
|
-
function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
4097
|
-
return writeSnapshotIfChanged(snapshot, rootDir).path;
|
|
4098
|
-
}
|
|
4099
|
-
function loadSurfaceSnapshots() {
|
|
4100
|
-
return listSnapshots();
|
|
4101
|
-
}
|
|
4102
|
-
function selectPortalSpec(renderedSet, portalId) {
|
|
4103
|
-
const hit = renderedSet.find((spec) => spec.portalId === portalId);
|
|
4104
|
-
if (!hit) {
|
|
4105
|
-
const known = renderedSet.map((s) => s.portalId).join(", ");
|
|
4106
|
-
throw new Error(`Unknown portal "${portalId}" \u2014 this surface renders: ${known || "(no portals)"}.`);
|
|
4107
|
-
}
|
|
4108
|
-
return [hit];
|
|
4109
|
-
}
|
|
4110
|
-
function perPortalPath(resolvedOut, portalId) {
|
|
4111
|
-
const ext = path11.extname(resolvedOut);
|
|
4112
|
-
const stem2 = ext ? resolvedOut.slice(0, -ext.length) : resolvedOut;
|
|
4113
|
-
return `${stem2}.${safeFilenamePart(portalId)}${ext}`;
|
|
4114
|
-
}
|
|
4115
|
-
function writeSurfaceFile(outPath, snapshot, renderedSet) {
|
|
4116
|
-
const resolved = path11.resolve(outPath);
|
|
4117
|
-
fs9.mkdirSync(path11.dirname(resolved), { recursive: true });
|
|
4118
|
-
if (!renderedSet || renderedSet.length === 0) {
|
|
4119
|
-
writeYamlFile(resolved, snapshot);
|
|
4120
|
-
return [resolved];
|
|
4121
|
-
}
|
|
4122
|
-
if (renderedSet.length === 1) {
|
|
4123
|
-
fs9.writeFileSync(resolved, renderedSet[0].document);
|
|
4124
|
-
return [resolved];
|
|
4125
|
-
}
|
|
4126
|
-
return renderedSet.map((spec) => {
|
|
4127
|
-
const target = perPortalPath(resolved, spec.portalId);
|
|
4128
|
-
fs9.writeFileSync(target, spec.document);
|
|
4129
|
-
return target;
|
|
4130
|
-
});
|
|
4131
|
-
}
|
|
4132
|
-
function exportResult(snapshot, renderedSet, writtenPaths) {
|
|
4133
|
-
const rendered = renderedSet?.length === 1 ? renderedSet[0].document : void 0;
|
|
4134
|
-
return {
|
|
4135
|
-
snapshot,
|
|
4136
|
-
...rendered !== void 0 ? { rendered } : {},
|
|
4137
|
-
...renderedSet ? { renderedSet } : {},
|
|
4138
|
-
...writtenPaths.length === 1 ? { writtenTo: writtenPaths[0] } : {},
|
|
4139
|
-
...writtenPaths.length ? { writtenPaths } : {}
|
|
4140
|
-
};
|
|
4141
|
-
}
|
|
4142
|
-
function exportSurface(maxAudience, format, outPath, portalId) {
|
|
4143
|
-
const snapshot = projectOwnSurface(maxAudience);
|
|
4144
|
-
let renderedSet = format === "openapi" ? toOpenApiSet(snapshot) : void 0;
|
|
4145
|
-
if (renderedSet && portalId) renderedSet = selectPortalSpec(renderedSet, portalId);
|
|
4146
|
-
const writtenPaths = outPath ? writeSurfaceFile(outPath, snapshot, renderedSet) : [];
|
|
4147
|
-
return exportResult(snapshot, renderedSet, writtenPaths);
|
|
4148
|
-
}
|
|
4149
|
-
function importSurface(sourcePath, origin) {
|
|
4150
|
-
const resolved = path11.resolve(sourcePath);
|
|
4151
|
-
if (!fs9.existsSync(resolved)) {
|
|
4152
|
-
throw new Error(`Surface document not found: ${resolved}`);
|
|
4153
|
-
}
|
|
4154
|
-
const body = fs9.readFileSync(resolved, "utf8");
|
|
4155
|
-
let snapshot;
|
|
4156
|
-
if (isOpenApiDocument(body)) {
|
|
4157
|
-
const projectName = path11.basename(resolved).replace(/\.(json|ya?ml)$/i, "");
|
|
4158
|
-
snapshot = fromOpenApi(body, projectName);
|
|
4159
|
-
snapshot = { ...snapshot, origin };
|
|
4160
|
-
} else {
|
|
4161
|
-
snapshot = SurfaceSnapshotSchema.parse(readYamlFile(resolved));
|
|
4162
|
-
snapshot = { ...snapshot, origin };
|
|
4163
|
-
}
|
|
4164
|
-
saveSnapshot(snapshot);
|
|
4165
|
-
return snapshot;
|
|
4166
|
-
}
|
|
4167
|
-
function generateChildSnapshots(rootDir = getProjectRoot()) {
|
|
4168
|
-
const topLevel = loadSubsystemSpecs().filter((s) => !s.id.includes("::"));
|
|
4169
|
-
const children = topLevel.filter((s) => s.projectPath);
|
|
4170
|
-
if (!children.length) return [];
|
|
4171
|
-
const familySnapshot = projectChildSurface();
|
|
4172
|
-
const siblingSnapshots = /* @__PURE__ */ new Map();
|
|
4173
|
-
const siblingSurface = (subsystemId) => {
|
|
4174
|
-
let snap = siblingSnapshots.get(subsystemId);
|
|
4175
|
-
if (!snap) {
|
|
4176
|
-
snap = projectSubsystemSurface(subsystemId);
|
|
4177
|
-
siblingSnapshots.set(subsystemId, snap);
|
|
4178
|
-
}
|
|
4179
|
-
return snap;
|
|
4180
|
-
};
|
|
4181
|
-
const written = [];
|
|
4182
|
-
const record2 = (r) => {
|
|
4183
|
-
if (r.changed) written.push(r.path);
|
|
4184
|
-
};
|
|
4185
|
-
for (const child of children) {
|
|
4186
|
-
const childDir = path11.resolve(rootDir, child.projectPath);
|
|
4187
|
-
if (!fs9.existsSync(childDir)) continue;
|
|
4188
|
-
record2(writeSnapshotIfChanged(familySnapshot, childDir));
|
|
4189
|
-
for (const sibling of topLevel) {
|
|
4190
|
-
if (sibling.id === child.id) continue;
|
|
4191
|
-
record2(writeSnapshotIfChanged(siblingSurface(sibling.id), childDir));
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
return written;
|
|
4195
|
-
}
|
|
4196
|
-
function computeParentStateId(parentRoot) {
|
|
4197
|
-
return computeStateIdAt(parentRoot);
|
|
4198
|
-
}
|
|
4199
|
-
function listExternalInterfaces() {
|
|
4200
|
-
const snapshots = listSnapshots();
|
|
4201
|
-
const chainingParent = resolveChainingParent();
|
|
4202
|
-
const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
|
|
4203
|
-
return snapshots.map((snapshot) => {
|
|
4204
|
-
const generated = snapshot.origin === "generated";
|
|
4205
|
-
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
4206
|
-
const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
|
|
4207
|
-
return {
|
|
4208
|
-
projectName: snapshot.projectName,
|
|
4209
|
-
origin: snapshot.origin,
|
|
4210
|
-
sourceKind,
|
|
4211
|
-
generatedAt: snapshot.generatedAt,
|
|
4212
|
-
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
4213
|
-
...snapshot.version ? { version: snapshot.version } : {},
|
|
4214
|
-
freshness,
|
|
4215
|
-
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
4216
|
-
};
|
|
4217
|
-
});
|
|
4218
|
-
}
|
|
4219
|
-
function surfaceContentKey(snapshot) {
|
|
4220
|
-
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
4221
|
-
return JSON.stringify(content);
|
|
4222
|
-
}
|
|
4223
|
-
function checkChildSurfaceFreshness(rootDir = getProjectRoot()) {
|
|
4224
|
-
const system = loadSystemSpec();
|
|
4225
|
-
if (!system) return [];
|
|
4226
|
-
const children = loadSubsystemSpecs().filter((s) => s.projectPath && !s.id.includes("::"));
|
|
4227
|
-
if (!children.length) return [];
|
|
4228
|
-
const current2 = surfaceContentKey(projectChildSurface());
|
|
4229
|
-
const issues = [];
|
|
4230
|
-
for (const child of children) {
|
|
4231
|
-
const childDir = path11.resolve(rootDir, child.projectPath);
|
|
4232
|
-
const held = getSnapshot(system.name, childDir);
|
|
4233
|
-
if (!held || held.origin !== "generated") continue;
|
|
4234
|
-
if (surfaceContentKey(held) !== current2) {
|
|
4235
|
-
issues.push({
|
|
4236
|
-
severity: "warning",
|
|
4237
|
-
code: "SURFACE_STALE",
|
|
4238
|
-
message: `Chained child "${child.id}" holds a parent surface snapshot whose contracts no longer match the current tree \u2014 rerun \`wairon surface generate-children\` so the child validates against the current surface.`,
|
|
4239
|
-
specId: child.id
|
|
4240
|
-
});
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
return issues;
|
|
4244
|
-
}
|
|
4245
|
-
var fs9, path11, SURFACES_DIRNAME, CROSS_BOUNDARY_TARGETS;
|
|
4246
|
-
var init_surfaces = __esm({
|
|
4247
|
-
"src/core/surfaces.ts"() {
|
|
4248
|
-
"use strict";
|
|
4249
|
-
fs9 = __toESM(require("fs"));
|
|
4250
|
-
path11 = __toESM(require("path"));
|
|
4251
|
-
init_fs();
|
|
4252
|
-
init_yaml();
|
|
4253
|
-
init_filenames();
|
|
4254
|
-
init_models();
|
|
4255
|
-
init_specs2();
|
|
4256
|
-
init_statehash();
|
|
4257
|
-
init_type_analysis();
|
|
4258
|
-
init_openapi();
|
|
4259
|
-
SURFACES_DIRNAME = "surfaces";
|
|
4260
|
-
CROSS_BOUNDARY_TARGETS = /* @__PURE__ */ new Set(["Portal", "Gateway", "Observer"]);
|
|
4261
|
-
}
|
|
4262
|
-
});
|
|
4263
|
-
|
|
4264
3550
|
// src/core/rules/namespace.ts
|
|
4265
3551
|
function isExternalNamespaceRef(ctx, ref) {
|
|
4266
3552
|
if (ref.startsWith("::") || ref.startsWith("super::")) return true;
|
|
@@ -4268,21 +3554,36 @@ function isExternalNamespaceRef(ctx, ref) {
|
|
|
4268
3554
|
if (sep9 === -1) return false;
|
|
4269
3555
|
return !ctx.subsystemIds.has(ref.slice(0, sep9));
|
|
4270
3556
|
}
|
|
4271
|
-
function resolveSurfaceRef(ctx, ref) {
|
|
3557
|
+
function resolveSurfaceRef(ctx, ref, fromSubsystem) {
|
|
4272
3558
|
const local = ref.split("::").filter((seg) => seg && seg !== "super").pop();
|
|
4273
3559
|
if (!local) return null;
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
3560
|
+
const mountPools = fromSubsystem ? enclosingMounts(ctx, fromSubsystem).reverse().map((ns) => ctx.mountSurfaceSnapshots.find((m) => m.namespace === ns)?.snapshots ?? []) : [];
|
|
3561
|
+
for (const pool of [...mountPools, ctx.surfaceSnapshots]) {
|
|
3562
|
+
for (const snapshot of pool) {
|
|
3563
|
+
const entry = snapshot.interfaces.find((e) => e.component === local || e.id === local);
|
|
3564
|
+
if (entry) return { snapshot, entry };
|
|
3565
|
+
}
|
|
4277
3566
|
}
|
|
4278
3567
|
return null;
|
|
4279
3568
|
}
|
|
4280
|
-
|
|
3569
|
+
function enclosingMounts(ctx, subsystemId) {
|
|
3570
|
+
const mounts = [];
|
|
3571
|
+
let prefix = "";
|
|
3572
|
+
for (const segment of subsystemId.split("::")) {
|
|
3573
|
+
prefix = prefix ? `${prefix}::${segment}` : segment;
|
|
3574
|
+
if (ctx.subsystems.some((s) => s.id === prefix && s.projectPath)) mounts.push(prefix);
|
|
3575
|
+
}
|
|
3576
|
+
return mounts;
|
|
3577
|
+
}
|
|
3578
|
+
function isCollapsedCrossTreeRef(ctx, ref, fromSubsystem) {
|
|
3579
|
+
const nearest = enclosingMounts(ctx, fromSubsystem).pop();
|
|
3580
|
+
return nearest !== void 0 && ref !== nearest && !ref.startsWith(`${nearest}::`);
|
|
3581
|
+
}
|
|
3582
|
+
var roundtripRule, namespaceHygieneRule;
|
|
4281
3583
|
var init_namespace = __esm({
|
|
4282
3584
|
"src/core/rules/namespace.ts"() {
|
|
4283
3585
|
"use strict";
|
|
4284
3586
|
init_specs2();
|
|
4285
|
-
init_surfaces();
|
|
4286
3587
|
roundtripRule = {
|
|
4287
3588
|
name: "roundtrip-serialization",
|
|
4288
3589
|
description: "Every loaded spec must re-serialize through the exact writer pipeline (same relativization, same schema, no I/O) \u2014 validate must predict every refusal that lock's status promotion or any later save would otherwise raise mid-write.",
|
|
@@ -4295,18 +3596,6 @@ var init_namespace = __esm({
|
|
|
4295
3596
|
}
|
|
4296
3597
|
}
|
|
4297
3598
|
};
|
|
4298
|
-
surfaceFreshnessRule = {
|
|
4299
|
-
name: "surface-freshness",
|
|
4300
|
-
description: "Every chained child's stored parent-surface snapshot must match the parent's CURRENT exported contracts \u2014 a drifted snapshot means the child validates standalone against a stale truth. Regenerate with `wairon surface generate-children`.",
|
|
4301
|
-
codes: [
|
|
4302
|
-
{ code: "SURFACE_STALE", defaultSeverity: "warning", summary: "A chained child holds a parent surface snapshot whose contracts no longer match the current tree" }
|
|
4303
|
-
],
|
|
4304
|
-
check(ctx) {
|
|
4305
|
-
for (const issue2 of checkChildSurfaceFreshness()) {
|
|
4306
|
-
ctx.addIssue("warning", "SURFACE_STALE", issue2.message, issue2.specId);
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
};
|
|
4310
3599
|
namespaceHygieneRule = {
|
|
4311
3600
|
name: "namespace-hygiene",
|
|
4312
3601
|
description: 'Ids must stay resolvable in the :: namespace grammar: no id segment may be the reserved keyword "super", and a subproject-local name must not shadow a root-level subsystem id \u2014 a bare reference to a shadowed name silently anchors to the ROOT subsystem, so the local spec becomes unaddressable.',
|
|
@@ -4413,11 +3702,13 @@ var init_contracts = __esm({
|
|
|
4413
3702
|
continue;
|
|
4414
3703
|
}
|
|
4415
3704
|
const isCrossTreeForm = isExternalNamespaceRef(ctx, step.targetComponent);
|
|
3705
|
+
const fromSubsystem = ctx.componentMap.get(contract.component)?.subsystem;
|
|
3706
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && isCollapsedCrossTreeRef(ctx, step.targetComponent, fromSubsystem);
|
|
4416
3707
|
if (step.type === "dispatch") {
|
|
4417
3708
|
const dispatchTarget = ctx.componentMap.get(step.targetComponent);
|
|
4418
3709
|
if (!dispatchTarget) {
|
|
4419
|
-
if (isCrossTreeForm) {
|
|
4420
|
-
const resolved = resolveSurfaceRef(ctx, step.targetComponent);
|
|
3710
|
+
if (isCrossTreeForm || isCollapsedForm) {
|
|
3711
|
+
const resolved = resolveSurfaceRef(ctx, step.targetComponent, fromSubsystem);
|
|
4421
3712
|
if (resolved) {
|
|
4422
3713
|
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
4423
3714
|
ctx.addIssue(
|
|
@@ -4431,10 +3722,12 @@ var init_contracts = __esm({
|
|
|
4431
3722
|
}
|
|
4432
3723
|
continue;
|
|
4433
3724
|
}
|
|
3725
|
+
}
|
|
3726
|
+
if (isCrossTreeForm) {
|
|
4434
3727
|
ctx.addIssue(
|
|
4435
3728
|
"warning",
|
|
4436
3729
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
4437
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, or import
|
|
3730
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
4438
3731
|
impl.id,
|
|
4439
3732
|
isDraftCtx
|
|
4440
3733
|
);
|
|
@@ -4474,8 +3767,8 @@ var init_contracts = __esm({
|
|
|
4474
3767
|
}
|
|
4475
3768
|
const targetComp = ctx.componentMap.get(step.targetComponent);
|
|
4476
3769
|
if (!targetComp) {
|
|
4477
|
-
if (isCrossTreeForm) {
|
|
4478
|
-
const resolved = resolveSurfaceRef(ctx, step.targetComponent);
|
|
3770
|
+
if (isCrossTreeForm || isCollapsedForm) {
|
|
3771
|
+
const resolved = resolveSurfaceRef(ctx, step.targetComponent, fromSubsystem);
|
|
4479
3772
|
if (resolved) {
|
|
4480
3773
|
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
4481
3774
|
if (!surfaceMethod) {
|
|
@@ -4504,10 +3797,12 @@ var init_contracts = __esm({
|
|
|
4504
3797
|
}
|
|
4505
3798
|
continue;
|
|
4506
3799
|
}
|
|
3800
|
+
}
|
|
3801
|
+
if (isCrossTreeForm) {
|
|
4507
3802
|
ctx.addIssue(
|
|
4508
3803
|
"warning",
|
|
4509
3804
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
4510
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, or import
|
|
3805
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${step.targetComponent}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
4511
3806
|
impl.id,
|
|
4512
3807
|
isDraftCtx
|
|
4513
3808
|
);
|
|
@@ -4995,7 +4290,7 @@ function resolveTypeScript(projectRoot2) {
|
|
|
4995
4290
|
return cached.ts;
|
|
4996
4291
|
}
|
|
4997
4292
|
let ts = null;
|
|
4998
|
-
const bases = [
|
|
4293
|
+
const bases = [path11.join(projectRoot2, "package.json"), __filename];
|
|
4999
4294
|
for (const base of bases) {
|
|
5000
4295
|
try {
|
|
5001
4296
|
const req = (0, import_module2.createRequire)(base);
|
|
@@ -5151,7 +4446,7 @@ function walkExact(ts, sourceText, fileName) {
|
|
|
5151
4446
|
}
|
|
5152
4447
|
function resolveRelativeModule(fromFile, specifier) {
|
|
5153
4448
|
if (!specifier.startsWith(".")) return null;
|
|
5154
|
-
const base =
|
|
4449
|
+
const base = path11.resolve(path11.dirname(fromFile), specifier);
|
|
5155
4450
|
const candidates = [
|
|
5156
4451
|
base,
|
|
5157
4452
|
base.replace(/\.js$/, ".ts"),
|
|
@@ -5159,12 +4454,12 @@ function resolveRelativeModule(fromFile, specifier) {
|
|
|
5159
4454
|
`${base}.ts`,
|
|
5160
4455
|
`${base}.tsx`,
|
|
5161
4456
|
`${base}.js`,
|
|
5162
|
-
|
|
5163
|
-
|
|
4457
|
+
path11.join(base, "index.ts"),
|
|
4458
|
+
path11.join(base, "index.js")
|
|
5164
4459
|
];
|
|
5165
4460
|
for (const c of candidates) {
|
|
5166
4461
|
try {
|
|
5167
|
-
if (
|
|
4462
|
+
if (fs9.statSync(c).isFile()) return c;
|
|
5168
4463
|
} catch {
|
|
5169
4464
|
}
|
|
5170
4465
|
}
|
|
@@ -5174,12 +4469,12 @@ function chaseStarExports(ts, facts, filePath, projectRoot2, visited, exactCache
|
|
|
5174
4469
|
for (const spec of facts.starExports) {
|
|
5175
4470
|
const target = resolveRelativeModule(filePath, spec);
|
|
5176
4471
|
if (!target || visited.has(target)) continue;
|
|
5177
|
-
if (
|
|
4472
|
+
if (path11.relative(projectRoot2, target).startsWith("..")) continue;
|
|
5178
4473
|
visited.add(target);
|
|
5179
4474
|
let targetFacts = exactCache.get(target);
|
|
5180
4475
|
if (targetFacts === void 0) {
|
|
5181
4476
|
try {
|
|
5182
|
-
targetFacts = walkExact(ts,
|
|
4477
|
+
targetFacts = walkExact(ts, fs9.readFileSync(target, "utf8"), target);
|
|
5183
4478
|
} catch {
|
|
5184
4479
|
targetFacts = null;
|
|
5185
4480
|
}
|
|
@@ -5219,23 +4514,23 @@ function buildCodeModel(implementations, projectRoot2) {
|
|
|
5219
4514
|
if (seen.has(sourcePath)) continue;
|
|
5220
4515
|
seen.add(sourcePath);
|
|
5221
4516
|
const empty = { declaredNames: [], anchoredNames: [], exportedNames: [], imports: [], reexports: [] };
|
|
5222
|
-
if (
|
|
4517
|
+
if (path11.isAbsolute(sourcePath) || path11.normalize(sourcePath).split(path11.sep)[0] === "..") {
|
|
5223
4518
|
files.push({ path: sourcePath, status: "escaped", ...empty });
|
|
5224
4519
|
continue;
|
|
5225
4520
|
}
|
|
5226
|
-
const absolute =
|
|
5227
|
-
if (
|
|
4521
|
+
const absolute = path11.resolve(projectRoot2, sourcePath);
|
|
4522
|
+
if (path11.relative(projectRoot2, absolute).startsWith("..")) {
|
|
5228
4523
|
files.push({ path: sourcePath, status: "escaped", ...empty });
|
|
5229
4524
|
continue;
|
|
5230
4525
|
}
|
|
5231
4526
|
let buffer;
|
|
5232
4527
|
try {
|
|
5233
|
-
const stat =
|
|
4528
|
+
const stat = fs9.statSync(absolute);
|
|
5234
4529
|
if (!stat.isFile()) {
|
|
5235
4530
|
files.push({ path: sourcePath, status: "missing", ...empty });
|
|
5236
4531
|
continue;
|
|
5237
4532
|
}
|
|
5238
|
-
buffer =
|
|
4533
|
+
buffer = fs9.readFileSync(absolute);
|
|
5239
4534
|
} catch {
|
|
5240
4535
|
files.push({ path: sourcePath, status: "missing", ...empty });
|
|
5241
4536
|
continue;
|
|
@@ -5245,7 +4540,7 @@ function buildCodeModel(implementations, projectRoot2) {
|
|
|
5245
4540
|
continue;
|
|
5246
4541
|
}
|
|
5247
4542
|
const text2 = buffer.toString("utf8");
|
|
5248
|
-
const language = EXTENSION_LANGUAGE[
|
|
4543
|
+
const language = EXTENSION_LANGUAGE[path11.extname(sourcePath).toLowerCase()];
|
|
5249
4544
|
let analyzed;
|
|
5250
4545
|
if (language === "typescript" || language === "javascript") {
|
|
5251
4546
|
const ts = resolveTypeScript(projectRoot2);
|
|
@@ -5279,12 +4574,12 @@ function buildCodeModel(implementations, projectRoot2) {
|
|
|
5279
4574
|
}
|
|
5280
4575
|
return { files, projectRoot: projectRoot2 };
|
|
5281
4576
|
}
|
|
5282
|
-
var
|
|
4577
|
+
var fs9, path11, import_module2, EXTENSION_LANGUAGE, C_FAMILY_COMMENTS, JS_PATTERNS, LANGUAGE_PATTERNS, PATTERN_ANALYSIS_MAX_BYTES, IDENTIFIER_RE, STRING_RE, tsResolutionCache, TS_RESOLUTION_RETRY_MS;
|
|
5283
4578
|
var init_source_analysis = __esm({
|
|
5284
4579
|
"src/core/source-analysis.ts"() {
|
|
5285
4580
|
"use strict";
|
|
5286
|
-
|
|
5287
|
-
|
|
4581
|
+
fs9 = __toESM(require("fs"));
|
|
4582
|
+
path11 = __toESM(require("path"));
|
|
5288
4583
|
import_module2 = require("module");
|
|
5289
4584
|
EXTENSION_LANGUAGE = {
|
|
5290
4585
|
".ts": "typescript",
|
|
@@ -6009,8 +5304,9 @@ var init_stereotype_deps = __esm({
|
|
|
6009
5304
|
for (const depId of dependencies) {
|
|
6010
5305
|
const depComp = ctx.componentMap.get(depId);
|
|
6011
5306
|
if (!depComp) {
|
|
6012
|
-
|
|
6013
|
-
|
|
5307
|
+
const external = isExternalNamespaceRef(ctx, depId);
|
|
5308
|
+
if (external || isCollapsedCrossTreeRef(ctx, depId, comp.subsystem)) {
|
|
5309
|
+
const resolved = resolveSurfaceRef(ctx, depId, comp.subsystem);
|
|
6014
5310
|
if (resolved) {
|
|
6015
5311
|
if (comp.componentType !== "Adapter") {
|
|
6016
5312
|
ctx.addIssue(
|
|
@@ -6024,14 +5320,16 @@ var init_stereotype_deps = __esm({
|
|
|
6024
5320
|
}
|
|
6025
5321
|
continue;
|
|
6026
5322
|
}
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
5323
|
+
if (external) {
|
|
5324
|
+
ctx.addIssue(
|
|
5325
|
+
"warning",
|
|
5326
|
+
"CROSS_TREE_REF_UNRESOLVED",
|
|
5327
|
+
`Component "${comp.id}" depends on cross-tree component "${depId}", and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
5328
|
+
comp.id,
|
|
5329
|
+
isDraftCtx
|
|
5330
|
+
);
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
6035
5333
|
}
|
|
6036
5334
|
ctx.addIssue(
|
|
6037
5335
|
"error",
|
|
@@ -8652,7 +7950,7 @@ var init_naming = __esm({
|
|
|
8652
7950
|
// src/core/rules/integration-conformance.ts
|
|
8653
7951
|
function resolveAgainst(mapped, fromFile, specifier) {
|
|
8654
7952
|
if (!specifier.startsWith(".")) return null;
|
|
8655
|
-
const joined = normalizePath(
|
|
7953
|
+
const joined = normalizePath(path12.posix.normalize(path12.posix.join(path12.posix.dirname(fromFile), specifier)));
|
|
8656
7954
|
const candidates = [
|
|
8657
7955
|
joined,
|
|
8658
7956
|
joined.replace(/\.js$/, ".ts"),
|
|
@@ -8668,11 +7966,11 @@ function resolveAgainst(mapped, fromFile, specifier) {
|
|
|
8668
7966
|
}
|
|
8669
7967
|
return null;
|
|
8670
7968
|
}
|
|
8671
|
-
var
|
|
7969
|
+
var path12, normalizePath, integrationConformanceRule;
|
|
8672
7970
|
var init_integration_conformance = __esm({
|
|
8673
7971
|
"src/core/rules/integration-conformance.ts"() {
|
|
8674
7972
|
"use strict";
|
|
8675
|
-
|
|
7973
|
+
path12 = __toESM(require("path"));
|
|
8676
7974
|
init_source_analysis();
|
|
8677
7975
|
init_conformance();
|
|
8678
7976
|
normalizePath = normalizeSourcePath;
|
|
@@ -8876,7 +8174,7 @@ var init_hidden_state = __esm({
|
|
|
8876
8174
|
// src/core/rules/dependency-conformance.ts
|
|
8877
8175
|
function resolveAgainst2(mapped, fromFile, specifier) {
|
|
8878
8176
|
if (!specifier.startsWith(".")) return null;
|
|
8879
|
-
const joined = normalizePath2(
|
|
8177
|
+
const joined = normalizePath2(path13.posix.normalize(path13.posix.join(path13.posix.dirname(fromFile), specifier)));
|
|
8880
8178
|
const candidates = [
|
|
8881
8179
|
joined,
|
|
8882
8180
|
joined.replace(/\.js$/, ".ts"),
|
|
@@ -8892,11 +8190,11 @@ function resolveAgainst2(mapped, fromFile, specifier) {
|
|
|
8892
8190
|
}
|
|
8893
8191
|
return null;
|
|
8894
8192
|
}
|
|
8895
|
-
var
|
|
8193
|
+
var path13, normalizePath2, dependencyConformanceRule;
|
|
8896
8194
|
var init_dependency_conformance = __esm({
|
|
8897
8195
|
"src/core/rules/dependency-conformance.ts"() {
|
|
8898
8196
|
"use strict";
|
|
8899
|
-
|
|
8197
|
+
path13 = __toESM(require("path"));
|
|
8900
8198
|
init_source_analysis();
|
|
8901
8199
|
init_conformance();
|
|
8902
8200
|
normalizePath2 = normalizeSourcePath;
|
|
@@ -9382,13 +8680,7 @@ function buildRuleContext(opts) {
|
|
|
9382
8680
|
...[...SDD_RULES, ...extensions.rules].flatMap((r) => r.codes.map((c) => c.code)),
|
|
9383
8681
|
// Declarative assertions bring their own namespaced codes — lint.allow
|
|
9384
8682
|
// and severity overrides treat them exactly like builtins.
|
|
9385
|
-
...extensions.assertions.map((a) => a.fullCode)
|
|
9386
|
-
// Entry-point emitted codes: validateSddTree's chained-subproject pass
|
|
9387
|
-
// raises these AFTER the rule run (it post-processes the aggregated issue
|
|
9388
|
-
// list), so no registered rule declares them — but lint.allow validation
|
|
9389
|
-
// must still recognize them as real codes.
|
|
9390
|
-
"CHAINED_SUBPROJECT_CONTEXT",
|
|
9391
|
-
"UNVERIFIED_EXTERNAL_REF"
|
|
8683
|
+
...extensions.assertions.map((a) => a.fullCode)
|
|
9392
8684
|
]);
|
|
9393
8685
|
const addIssue = (defaultSeverity, code, message, specId, isDraftContext, surfaceResolved) => {
|
|
9394
8686
|
if (scopeSubsystem && specId && !isSpecInScope(specId)) {
|
|
@@ -9444,6 +8736,7 @@ function buildRuleContext(opts) {
|
|
|
9444
8736
|
ext: { profiles: extensions.profiles, languages: extensions.languages, patterns: extensions.patterns, guarantees: extensions.guarantees, assertions: extensions.assertions, packSelections: opts.packSelections ?? [], selectionFailures: extensions.selectionFailures ?? [] },
|
|
9445
8737
|
variants: opts.variants ?? [],
|
|
9446
8738
|
surfaceSnapshots: opts.surfaceSnapshots ?? [],
|
|
8739
|
+
mountSurfaceSnapshots: opts.mountSurfaceSnapshots ?? [],
|
|
9447
8740
|
codeModel: opts.codeModel ?? emptyCodeModel(),
|
|
9448
8741
|
lintAllows,
|
|
9449
8742
|
knownIssueCodes,
|
|
@@ -9501,7 +8794,6 @@ var init_rules = __esm({
|
|
|
9501
8794
|
// explain many downstream findings, so surface them early in the list.
|
|
9502
8795
|
namespaceHygieneRule,
|
|
9503
8796
|
roundtripRule,
|
|
9504
|
-
surfaceFreshnessRule,
|
|
9505
8797
|
typeReferencesRule,
|
|
9506
8798
|
contractsRule,
|
|
9507
8799
|
// Vocabulary check right after contracts: an unknown token explains why the
|
|
@@ -9771,14 +9063,14 @@ function describeApprover(who) {
|
|
|
9771
9063
|
}
|
|
9772
9064
|
function readLockRecord() {
|
|
9773
9065
|
try {
|
|
9774
|
-
return normalizeRecord(JSON.parse(
|
|
9066
|
+
return normalizeRecord(JSON.parse(fs10.readFileSync(lockPath(), "utf8")));
|
|
9775
9067
|
} catch {
|
|
9776
9068
|
return null;
|
|
9777
9069
|
}
|
|
9778
9070
|
}
|
|
9779
9071
|
function readLockRecordAt(root) {
|
|
9780
9072
|
try {
|
|
9781
|
-
return normalizeRecord(JSON.parse(
|
|
9073
|
+
return normalizeRecord(JSON.parse(fs10.readFileSync(aiDirAt(root, "lock.json"), "utf8")));
|
|
9782
9074
|
} catch {
|
|
9783
9075
|
return null;
|
|
9784
9076
|
}
|
|
@@ -9789,10 +9081,10 @@ function normalizeRecord(raw) {
|
|
|
9789
9081
|
}
|
|
9790
9082
|
function writeLockRecord(record2) {
|
|
9791
9083
|
const p = lockPath();
|
|
9792
|
-
|
|
9084
|
+
fs10.mkdirSync(path14.dirname(p), { recursive: true });
|
|
9793
9085
|
const tmp = `${p}.tmp`;
|
|
9794
|
-
|
|
9795
|
-
|
|
9086
|
+
fs10.writeFileSync(tmp, JSON.stringify(withSortedMaps(record2), null, 2) + "\n");
|
|
9087
|
+
fs10.renameSync(tmp, p);
|
|
9796
9088
|
}
|
|
9797
9089
|
function withSortedMaps(record2) {
|
|
9798
9090
|
const sorted = (m) => {
|
|
@@ -9809,12 +9101,12 @@ function withSortedMaps(record2) {
|
|
|
9809
9101
|
...children ? { children } : {}
|
|
9810
9102
|
};
|
|
9811
9103
|
}
|
|
9812
|
-
var
|
|
9104
|
+
var fs10, path14;
|
|
9813
9105
|
var init_lockfile = __esm({
|
|
9814
9106
|
"src/core/lockfile.ts"() {
|
|
9815
9107
|
"use strict";
|
|
9816
|
-
|
|
9817
|
-
|
|
9108
|
+
fs10 = __toESM(require("fs"));
|
|
9109
|
+
path14 = __toESM(require("path"));
|
|
9818
9110
|
init_fs();
|
|
9819
9111
|
}
|
|
9820
9112
|
});
|
|
@@ -10547,14 +9839,14 @@ function embedJson(value) {
|
|
|
10547
9839
|
}
|
|
10548
9840
|
function loadCytoscapeLib() {
|
|
10549
9841
|
const candidates = [
|
|
10550
|
-
|
|
9842
|
+
path15.resolve(__dirname, "..", "templates", "canvas", "cytoscape.min.js"),
|
|
10551
9843
|
// src/core & dist/cli
|
|
10552
|
-
|
|
9844
|
+
path15.resolve(__dirname, "templates", "canvas", "cytoscape.min.js")
|
|
10553
9845
|
// dist (library entry)
|
|
10554
9846
|
];
|
|
10555
9847
|
for (const p of candidates) {
|
|
10556
|
-
if (
|
|
10557
|
-
return
|
|
9848
|
+
if (fs11.existsSync(p)) {
|
|
9849
|
+
return fs11.readFileSync(p, "utf-8").replace(/<\/script/gi, "<\\/script");
|
|
10558
9850
|
}
|
|
10559
9851
|
}
|
|
10560
9852
|
throw new Error("Vendored cytoscape bundle not found (templates/canvas/cytoscape.min.js) \u2014 the wairon installation is incomplete.");
|
|
@@ -10566,12 +9858,12 @@ function renderCanvasHtml(model) {
|
|
|
10566
9858
|
function escapeHtml(s) {
|
|
10567
9859
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
10568
9860
|
}
|
|
10569
|
-
var
|
|
9861
|
+
var fs11, path15, CANVAS_TEMPLATE;
|
|
10570
9862
|
var init_canvas = __esm({
|
|
10571
9863
|
"src/core/canvas.ts"() {
|
|
10572
9864
|
"use strict";
|
|
10573
|
-
|
|
10574
|
-
|
|
9865
|
+
fs11 = __toESM(require("fs"));
|
|
9866
|
+
path15 = __toESM(require("path"));
|
|
10575
9867
|
init_specs2();
|
|
10576
9868
|
init_type_analysis();
|
|
10577
9869
|
init_diagram_export();
|
|
@@ -14657,93 +13949,799 @@ var MODEL = __MODEL_JSON__;
|
|
|
14657
13949
|
b.addEventListener('click', function () { navigateTo(b.getAttribute('data-open-kind'), b.getAttribute('data-open-id')); });
|
|
14658
13950
|
})(opens[k]);
|
|
14659
13951
|
}
|
|
14660
|
-
var typesOpen = document.getElementById('openTypesBtn');
|
|
14661
|
-
if (typesOpen && typesOpen.addEventListener && panel.innerHTML.indexOf('openTypesBtn') >= 0) {
|
|
14662
|
-
typesOpen.addEventListener('click', function () { navigateTo('types', null); });
|
|
13952
|
+
var typesOpen = document.getElementById('openTypesBtn');
|
|
13953
|
+
if (typesOpen && typesOpen.addEventListener && panel.innerHTML.indexOf('openTypesBtn') >= 0) {
|
|
13954
|
+
typesOpen.addEventListener('click', function () { navigateTo('types', null); });
|
|
13955
|
+
}
|
|
13956
|
+
var oapis = panel.querySelectorAll('[data-openapi-tag]');
|
|
13957
|
+
for (var oi = 0; oi < oapis.length; oi++) {
|
|
13958
|
+
(function (b) {
|
|
13959
|
+
b.addEventListener('click', function () {
|
|
13960
|
+
var tag = b.getAttribute('data-openapi-tag') || '';
|
|
13961
|
+
if (typeof opts !== 'undefined' && opts && opts.onOpenApi) { opts.onOpenApi(tag); return; }
|
|
13962
|
+
var href = openApiSiblingHref();
|
|
13963
|
+
if (href) window.open(href + (tag ? '#/' + tag : ''), '_blank', 'noopener');
|
|
13964
|
+
});
|
|
13965
|
+
})(oapis[oi]);
|
|
13966
|
+
}
|
|
13967
|
+
var ospecs = panel.querySelectorAll('[data-openspec-kind]');
|
|
13968
|
+
for (var si = 0; si < ospecs.length; si++) {
|
|
13969
|
+
(function (b) {
|
|
13970
|
+
b.addEventListener('click', function () {
|
|
13971
|
+
if (typeof opts !== 'undefined' && opts && typeof opts.onOpenSpec === 'function') {
|
|
13972
|
+
opts.onOpenSpec(b.getAttribute('data-openspec-kind'), b.getAttribute('data-openspec-id') || '');
|
|
13973
|
+
}
|
|
13974
|
+
});
|
|
13975
|
+
})(ospecs[si]);
|
|
13976
|
+
}
|
|
13977
|
+
var flows = panel.querySelectorAll('[data-flow-comp]');
|
|
13978
|
+
for (var j = 0; j < flows.length; j++) {
|
|
13979
|
+
(function (b) {
|
|
13980
|
+
b.addEventListener('click', function (ev) {
|
|
13981
|
+
if (ev && ev.stopPropagation) ev.stopPropagation();
|
|
13982
|
+
openFlow(b.getAttribute('data-flow-comp'), b.getAttribute('data-flow-method'), b.getAttribute('data-flow-mode') || 'flow');
|
|
13983
|
+
});
|
|
13984
|
+
})(flows[j]);
|
|
13985
|
+
}
|
|
13986
|
+
}
|
|
13987
|
+
|
|
13988
|
+
renderPanel();
|
|
13989
|
+
// Stage G: a deep link may focus a component and/or open a method's narrative
|
|
13990
|
+
// modal once the seeded view + DOM + cy graph exist. The host parses the URL hash
|
|
13991
|
+
// into opts.initialSelect / opts.initialFlow \u2014 both carry the component id, so this
|
|
13992
|
+
// works whether the seeded view is the component itself or its parent subsystem
|
|
13993
|
+
// (a leaf component has no meaningful "inside", so Specs deep-links open the parent
|
|
13994
|
+
// and focus the component here).
|
|
13995
|
+
(function () {
|
|
13996
|
+
if (typeof opts === 'undefined' || !opts) return;
|
|
13997
|
+
var f = opts.initialFlow, s = opts.initialSelect;
|
|
13998
|
+
var focusComp = (f && f.comp) || (s && s.comp);
|
|
13999
|
+
if (focusComp && compById[focusComp]) {
|
|
14000
|
+
try { select('component', focusComp, true); } catch (e) { /* ignore */ }
|
|
14001
|
+
}
|
|
14002
|
+
if (f && f.comp && f.method && compById[f.comp]) {
|
|
14003
|
+
try { openFlow(f.comp, f.method, f.mode === 'steps' ? 'steps' : 'flow'); } catch (e) { /* ignore */ }
|
|
14004
|
+
}
|
|
14005
|
+
})();
|
|
14006
|
+
})();
|
|
14007
|
+
</script>
|
|
14008
|
+
</body>
|
|
14009
|
+
</html>
|
|
14010
|
+
`;
|
|
14011
|
+
}
|
|
14012
|
+
});
|
|
14013
|
+
|
|
14014
|
+
// src/core/rules/repository.ts
|
|
14015
|
+
function addRule(rule) {
|
|
14016
|
+
ruleSet.push(rule);
|
|
14017
|
+
}
|
|
14018
|
+
function registerBuiltinRules() {
|
|
14019
|
+
ruleSet = [];
|
|
14020
|
+
for (const rule of SDD_RULES) addRule(rule);
|
|
14021
|
+
}
|
|
14022
|
+
function registerPackRules(packRules) {
|
|
14023
|
+
for (const rule of packRules) addRule(rule);
|
|
14024
|
+
}
|
|
14025
|
+
function ruleSequence() {
|
|
14026
|
+
const base = ruleSet.filter((r) => r !== lintAllowsRule);
|
|
14027
|
+
return ruleSet.includes(lintAllowsRule) ? [...base, lintAllowsRule] : base;
|
|
14028
|
+
}
|
|
14029
|
+
function specScopedRules() {
|
|
14030
|
+
return ruleSequence().filter((r) => r.scope === "spec");
|
|
14031
|
+
}
|
|
14032
|
+
var ruleSet;
|
|
14033
|
+
var init_repository = __esm({
|
|
14034
|
+
"src/core/rules/repository.ts"() {
|
|
14035
|
+
"use strict";
|
|
14036
|
+
init_rules();
|
|
14037
|
+
init_lint_allows();
|
|
14038
|
+
ruleSet = [];
|
|
14039
|
+
}
|
|
14040
|
+
});
|
|
14041
|
+
|
|
14042
|
+
// src/utils/filenames.ts
|
|
14043
|
+
function safeFilenamePart(value) {
|
|
14044
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
14045
|
+
}
|
|
14046
|
+
var init_filenames = __esm({
|
|
14047
|
+
"src/utils/filenames.ts"() {
|
|
14048
|
+
"use strict";
|
|
14049
|
+
}
|
|
14050
|
+
});
|
|
14051
|
+
|
|
14052
|
+
// src/core/openapi.ts
|
|
14053
|
+
function schemaFor(typeRef, closureIds) {
|
|
14054
|
+
const trimmed = typeRef.trim().replace(/^promise\s*<(.+)>$/i, "$1").trim();
|
|
14055
|
+
const arrayMatch = /^(.+)\[\]$/.exec(trimmed);
|
|
14056
|
+
if (arrayMatch) {
|
|
14057
|
+
return { type: "array", items: schemaFor(arrayMatch[1], closureIds) };
|
|
14058
|
+
}
|
|
14059
|
+
const lower = trimmed.toLowerCase();
|
|
14060
|
+
if (lower in PRIMITIVES) {
|
|
14061
|
+
const p = PRIMITIVES[lower];
|
|
14062
|
+
return p.type ? { ...p } : {};
|
|
14063
|
+
}
|
|
14064
|
+
const local = trimmed.split(/::|\./).pop().toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
14065
|
+
const hit = [...closureIds].find((id) => id.toLowerCase() === local);
|
|
14066
|
+
if (hit) return { $ref: `#/components/schemas/${hit}` };
|
|
14067
|
+
return { type: "object", description: `Unresolved type: ${trimmed}` };
|
|
14068
|
+
}
|
|
14069
|
+
function operationFor(method2, closureIds) {
|
|
14070
|
+
const endpoint = method2.endpoint;
|
|
14071
|
+
const httpVerb = endpoint && endpoint.transport === "HTTP" ? endpoint.method.toLowerCase() : "post";
|
|
14072
|
+
const bodyVerbs = /* @__PURE__ */ new Set(["post", "put", "patch"]);
|
|
14073
|
+
const params = method2.params ?? [];
|
|
14074
|
+
const op = {
|
|
14075
|
+
operationId: method2.name,
|
|
14076
|
+
summary: method2.description,
|
|
14077
|
+
responses: {
|
|
14078
|
+
"200": {
|
|
14079
|
+
description: method2.returns || "Success",
|
|
14080
|
+
...method2.returns && method2.returns.toLowerCase() !== "void" ? { content: { "application/json": { schema: schemaFor(method2.returns, closureIds) } } } : {}
|
|
14081
|
+
}
|
|
14082
|
+
}
|
|
14083
|
+
};
|
|
14084
|
+
if (method2.guarantees?.length) op["x-wairon-guarantees"] = method2.guarantees;
|
|
14085
|
+
if (method2.effect) op["x-wairon-effect"] = method2.effect;
|
|
14086
|
+
if (method2.ext && Object.keys(method2.ext).length) op["x-wairon-ext"] = method2.ext;
|
|
14087
|
+
if (params.length) {
|
|
14088
|
+
if (bodyVerbs.has(httpVerb)) {
|
|
14089
|
+
op.requestBody = {
|
|
14090
|
+
required: true,
|
|
14091
|
+
content: {
|
|
14092
|
+
"application/json": {
|
|
14093
|
+
schema: {
|
|
14094
|
+
type: "object",
|
|
14095
|
+
properties: Object.fromEntries(params.map((p) => [p.name, schemaFor(p.type, closureIds)])),
|
|
14096
|
+
required: params.filter((p) => !p.optional).map((p) => p.name)
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14100
|
+
};
|
|
14101
|
+
} else {
|
|
14102
|
+
op.parameters = params.map((p) => ({
|
|
14103
|
+
name: p.name,
|
|
14104
|
+
in: "query",
|
|
14105
|
+
required: !p.optional,
|
|
14106
|
+
...p.description ? { description: p.description } : {},
|
|
14107
|
+
schema: schemaFor(p.type, closureIds)
|
|
14108
|
+
}));
|
|
14109
|
+
}
|
|
14110
|
+
}
|
|
14111
|
+
return op;
|
|
14112
|
+
}
|
|
14113
|
+
function securitySchemeObject(auth) {
|
|
14114
|
+
if (auth.scheme === "none") return null;
|
|
14115
|
+
const desc = auth.description ? { description: auth.description } : {};
|
|
14116
|
+
switch (auth.scheme) {
|
|
14117
|
+
case "apiKey":
|
|
14118
|
+
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "X-API-Key", ...desc };
|
|
14119
|
+
case "bearer":
|
|
14120
|
+
return { type: "http", scheme: "bearer", ...auth.bearerFormat ? { bearerFormat: auth.bearerFormat } : {}, ...desc };
|
|
14121
|
+
case "basic":
|
|
14122
|
+
return { type: "http", scheme: "basic", ...desc };
|
|
14123
|
+
case "oauth2": {
|
|
14124
|
+
const flow = { scopes: Object.fromEntries((auth.scopes ?? []).map((s) => [s.name, s.description])) };
|
|
14125
|
+
if (auth.authorizationUrl) flow.authorizationUrl = auth.authorizationUrl;
|
|
14126
|
+
if (auth.tokenUrl) flow.tokenUrl = auth.tokenUrl;
|
|
14127
|
+
if (auth.refreshUrl) flow.refreshUrl = auth.refreshUrl;
|
|
14128
|
+
return { type: "oauth2", flows: { [auth.flow ?? "authorizationCode"]: flow }, ...desc };
|
|
14129
|
+
}
|
|
14130
|
+
case "openIdConnect":
|
|
14131
|
+
return { type: "openIdConnect", openIdConnectUrl: auth.openIdConnectUrl ?? "", ...desc };
|
|
14132
|
+
case "custom":
|
|
14133
|
+
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "Authorization", description: auth.description ?? auth.example ?? "Custom authentication scheme." };
|
|
14134
|
+
default:
|
|
14135
|
+
return null;
|
|
14136
|
+
}
|
|
14137
|
+
}
|
|
14138
|
+
function schemeBaseName(scheme) {
|
|
14139
|
+
return { apiKey: "ApiKeyAuth", bearer: "BearerAuth", basic: "BasicAuth", oauth2: "OAuth2", openIdConnect: "OpenIdConnect", custom: "CustomAuth" }[scheme] ?? "Auth";
|
|
14140
|
+
}
|
|
14141
|
+
function buildSecurity(entries) {
|
|
14142
|
+
const schemes = {};
|
|
14143
|
+
const nameByContent = /* @__PURE__ */ new Map();
|
|
14144
|
+
const securityByEntry = /* @__PURE__ */ new Map();
|
|
14145
|
+
for (const entry of entries) {
|
|
14146
|
+
if (!entry.auth) continue;
|
|
14147
|
+
const obj = securitySchemeObject(entry.auth);
|
|
14148
|
+
if (!obj) continue;
|
|
14149
|
+
const content = JSON.stringify(obj);
|
|
14150
|
+
let name = nameByContent.get(content);
|
|
14151
|
+
if (!name) {
|
|
14152
|
+
name = schemeBaseName(entry.auth.scheme);
|
|
14153
|
+
for (let n = 2; schemes[name]; n++) name = schemeBaseName(entry.auth.scheme) + n;
|
|
14154
|
+
schemes[name] = obj;
|
|
14155
|
+
nameByContent.set(content, name);
|
|
14156
|
+
}
|
|
14157
|
+
const scopeNames = entry.auth.scheme === "oauth2" ? (entry.auth.scopes ?? []).map((s) => s.name) : [];
|
|
14158
|
+
securityByEntry.set(entry.id, { [name]: scopeNames });
|
|
14159
|
+
}
|
|
14160
|
+
return { schemes, securityByEntry };
|
|
14161
|
+
}
|
|
14162
|
+
function httpEntriesOf(snapshot) {
|
|
14163
|
+
return snapshot.interfaces.filter((e) => e.type === "REST" || e.methods.some((m) => m.endpoint?.transport === "HTTP"));
|
|
14164
|
+
}
|
|
14165
|
+
function renderDoc(snapshot, entries, closureIds, opts = {}) {
|
|
14166
|
+
const { schemes, securityByEntry } = buildSecurity(entries);
|
|
14167
|
+
const paths = {};
|
|
14168
|
+
for (const entry of entries) {
|
|
14169
|
+
const security = securityByEntry.get(entry.id);
|
|
14170
|
+
for (const method2 of entry.methods) {
|
|
14171
|
+
const endpoint = method2.endpoint;
|
|
14172
|
+
if (!endpoint || endpoint.transport !== "HTTP") continue;
|
|
14173
|
+
const p = endpoint.path.startsWith("/") ? endpoint.path : `/${endpoint.path}`;
|
|
14174
|
+
paths[p] = paths[p] ?? {};
|
|
14175
|
+
paths[p][endpoint.method.toLowerCase()] = {
|
|
14176
|
+
tags: [entry.id],
|
|
14177
|
+
...operationFor(method2, closureIds),
|
|
14178
|
+
...security ? { security: [security] } : {}
|
|
14179
|
+
};
|
|
14180
|
+
}
|
|
14181
|
+
}
|
|
14182
|
+
const schemas = {};
|
|
14183
|
+
for (const t of snapshot.types) {
|
|
14184
|
+
schemas[t.id] = {
|
|
14185
|
+
type: "object",
|
|
14186
|
+
title: t.name,
|
|
14187
|
+
properties: Object.fromEntries(t.fields.map((f) => [f.name, schemaFor(f.type, closureIds)])),
|
|
14188
|
+
required: t.fields.filter((f) => !f.optional).map((f) => f.name)
|
|
14189
|
+
};
|
|
14190
|
+
}
|
|
14191
|
+
const components = {};
|
|
14192
|
+
if (Object.keys(schemas).length) components.schemas = schemas;
|
|
14193
|
+
if (Object.keys(schemes).length) components.securitySchemes = schemes;
|
|
14194
|
+
const basePaths = [...new Set(entries.map((e) => e.basePath).filter((b) => !!b))];
|
|
14195
|
+
const servers = opts.servers && basePaths.length === 1 ? [{ url: basePaths[0] }] : void 0;
|
|
14196
|
+
return {
|
|
14197
|
+
openapi: "3.1.0",
|
|
14198
|
+
info: {
|
|
14199
|
+
title: opts.title ?? snapshot.projectName,
|
|
14200
|
+
version: snapshot.version ?? "0.0.0",
|
|
14201
|
+
...snapshot.stateId ? { "x-wairon-state-id": snapshot.stateId } : {},
|
|
14202
|
+
"x-wairon-origin": snapshot.origin,
|
|
14203
|
+
"x-wairon-generated-at": snapshot.generatedAt
|
|
14204
|
+
},
|
|
14205
|
+
...servers ? { servers } : {},
|
|
14206
|
+
paths,
|
|
14207
|
+
...Object.keys(components).length ? { components } : {}
|
|
14208
|
+
};
|
|
14209
|
+
}
|
|
14210
|
+
function toOpenApiSet(snapshot) {
|
|
14211
|
+
const closureIds = new Set(snapshot.types.map((t) => t.id));
|
|
14212
|
+
const byPortal = /* @__PURE__ */ new Map();
|
|
14213
|
+
const order = [];
|
|
14214
|
+
for (const entry of httpEntriesOf(snapshot)) {
|
|
14215
|
+
if (!byPortal.has(entry.component)) {
|
|
14216
|
+
byPortal.set(entry.component, []);
|
|
14217
|
+
order.push(entry.component);
|
|
14218
|
+
}
|
|
14219
|
+
byPortal.get(entry.component).push(entry);
|
|
14220
|
+
}
|
|
14221
|
+
return order.map((portalId) => {
|
|
14222
|
+
const entries = byPortal.get(portalId);
|
|
14223
|
+
const name = entries[0]?.name ?? portalId;
|
|
14224
|
+
return { portalId, name, document: JSON.stringify(renderDoc(snapshot, entries, closureIds, { title: name, servers: true }), null, 2) };
|
|
14225
|
+
});
|
|
14226
|
+
}
|
|
14227
|
+
function isOpenApiDocument(body) {
|
|
14228
|
+
try {
|
|
14229
|
+
const parsed = yaml2.load(body);
|
|
14230
|
+
return !!parsed && typeof parsed === "object" && typeof parsed.openapi === "string";
|
|
14231
|
+
} catch {
|
|
14232
|
+
return false;
|
|
14233
|
+
}
|
|
14234
|
+
}
|
|
14235
|
+
function typeRefFromSchema(schema) {
|
|
14236
|
+
if (!schema) return "json";
|
|
14237
|
+
const ref = schema.$ref;
|
|
14238
|
+
if (typeof ref === "string") return ref.split("/").pop() ?? "json";
|
|
14239
|
+
if (schema.type === "array") {
|
|
14240
|
+
return `${typeRefFromSchema(schema.items)}[]`;
|
|
14241
|
+
}
|
|
14242
|
+
const t = schema.type;
|
|
14243
|
+
if (t === "integer") return "int";
|
|
14244
|
+
if (typeof t === "string" && t !== "object") return t;
|
|
14245
|
+
return "json";
|
|
14246
|
+
}
|
|
14247
|
+
function authFromSecurityScheme(scheme) {
|
|
14248
|
+
const desc = typeof scheme.description === "string" ? { description: scheme.description } : {};
|
|
14249
|
+
if (scheme.type === "apiKey") {
|
|
14250
|
+
return {
|
|
14251
|
+
scheme: "apiKey",
|
|
14252
|
+
...scheme.in === "header" || scheme.in === "query" || scheme.in === "cookie" ? { in: scheme.in } : {},
|
|
14253
|
+
...typeof scheme.name === "string" ? { name: scheme.name } : {},
|
|
14254
|
+
...desc
|
|
14255
|
+
};
|
|
14256
|
+
}
|
|
14257
|
+
if (scheme.type === "http") {
|
|
14258
|
+
if (scheme.scheme === "bearer") return { scheme: "bearer", ...typeof scheme.bearerFormat === "string" ? { bearerFormat: scheme.bearerFormat } : {}, ...desc };
|
|
14259
|
+
if (scheme.scheme === "basic") return { scheme: "basic", ...desc };
|
|
14260
|
+
}
|
|
14261
|
+
if (scheme.type === "oauth2") {
|
|
14262
|
+
const flows = scheme.flows ?? {};
|
|
14263
|
+
const flowKey = Object.keys(flows)[0];
|
|
14264
|
+
const f = flows[flowKey] ?? {};
|
|
14265
|
+
return {
|
|
14266
|
+
scheme: "oauth2",
|
|
14267
|
+
...flowKey === "authorizationCode" || flowKey === "clientCredentials" || flowKey === "implicit" || flowKey === "password" ? { flow: flowKey } : {},
|
|
14268
|
+
...typeof f.authorizationUrl === "string" ? { authorizationUrl: f.authorizationUrl } : {},
|
|
14269
|
+
...typeof f.tokenUrl === "string" ? { tokenUrl: f.tokenUrl } : {},
|
|
14270
|
+
...typeof f.refreshUrl === "string" ? { refreshUrl: f.refreshUrl } : {},
|
|
14271
|
+
scopes: Object.entries(f.scopes ?? {}).map(([name, description]) => ({ name, description })),
|
|
14272
|
+
...desc
|
|
14273
|
+
};
|
|
14274
|
+
}
|
|
14275
|
+
if (scheme.type === "openIdConnect") {
|
|
14276
|
+
return { scheme: "openIdConnect", openIdConnectUrl: typeof scheme.openIdConnectUrl === "string" ? scheme.openIdConnectUrl : "", ...desc };
|
|
14277
|
+
}
|
|
14278
|
+
return void 0;
|
|
14279
|
+
}
|
|
14280
|
+
function fromOpenApi(document, projectName) {
|
|
14281
|
+
let parsed;
|
|
14282
|
+
try {
|
|
14283
|
+
parsed = yaml2.load(document);
|
|
14284
|
+
} catch (e) {
|
|
14285
|
+
throw new Error(`Invalid surface document: not parseable as JSON/YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
14286
|
+
}
|
|
14287
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.openapi !== "string" || typeof parsed.paths !== "object") {
|
|
14288
|
+
throw new Error('Invalid surface document: missing OpenAPI "openapi"/"paths" structure.');
|
|
14289
|
+
}
|
|
14290
|
+
const info = parsed.info ?? {};
|
|
14291
|
+
const methods = [];
|
|
14292
|
+
for (const [rawPath, ops] of Object.entries(parsed.paths)) {
|
|
14293
|
+
for (const [verb, opRaw] of Object.entries(ops ?? {})) {
|
|
14294
|
+
if (!["get", "post", "put", "delete", "patch", "options", "head"].includes(verb)) continue;
|
|
14295
|
+
const op = opRaw ?? {};
|
|
14296
|
+
const name = typeof op.operationId === "string" && /^[a-zA-Z0-9_]+$/.test(op.operationId) ? op.operationId : `${verb}_${rawPath.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
14297
|
+
const params = [];
|
|
14298
|
+
for (const p of op.parameters ?? []) {
|
|
14299
|
+
if (typeof p.name !== "string") continue;
|
|
14300
|
+
params.push({
|
|
14301
|
+
name: p.name,
|
|
14302
|
+
type: typeRefFromSchema(p.schema),
|
|
14303
|
+
...p.required === true ? {} : { optional: true },
|
|
14304
|
+
...typeof p.description === "string" ? { description: p.description } : {}
|
|
14305
|
+
});
|
|
14306
|
+
}
|
|
14307
|
+
const bodySchema = op.requestBody?.content?.["application/json"]?.schema;
|
|
14308
|
+
if (bodySchema) {
|
|
14309
|
+
const props = bodySchema.properties ?? {};
|
|
14310
|
+
const required = new Set(bodySchema.required ?? []);
|
|
14311
|
+
if (Object.keys(props).length) {
|
|
14312
|
+
for (const [pname, pschema] of Object.entries(props)) {
|
|
14313
|
+
params.push({ name: pname, type: typeRefFromSchema(pschema), ...required.has(pname) ? {} : { optional: true } });
|
|
14314
|
+
}
|
|
14315
|
+
} else {
|
|
14316
|
+
params.push({ name: "body", type: typeRefFromSchema(bodySchema) });
|
|
14317
|
+
}
|
|
14318
|
+
}
|
|
14319
|
+
const okResponse = op.responses?.["200"] ?? op.responses?.["201"];
|
|
14320
|
+
const responseSchema = okResponse?.content?.["application/json"]?.schema;
|
|
14321
|
+
const returns = responseSchema ? typeRefFromSchema(responseSchema) : "void";
|
|
14322
|
+
const rawGuarantees = op["x-wairon-guarantees"];
|
|
14323
|
+
const guarantees = Array.isArray(rawGuarantees) ? rawGuarantees.filter((g) => typeof g === "string" && g.length > 0) : [];
|
|
14324
|
+
const rawEffect = op["x-wairon-effect"];
|
|
14325
|
+
const effect = rawEffect === "read" || rawEffect === "write" ? rawEffect : void 0;
|
|
14326
|
+
const rawExt = op["x-wairon-ext"];
|
|
14327
|
+
const ext = rawExt && typeof rawExt === "object" && !Array.isArray(rawExt) ? rawExt : void 0;
|
|
14328
|
+
methods.push({
|
|
14329
|
+
name,
|
|
14330
|
+
description: typeof op.summary === "string" ? op.summary : typeof op.description === "string" ? op.description : name,
|
|
14331
|
+
signature: `${name}(${params.map((p) => `${p.name}: ${p.type}`).join(", ")}): ${returns}`,
|
|
14332
|
+
returns,
|
|
14333
|
+
params,
|
|
14334
|
+
endpoint: { transport: "HTTP", method: verb.toUpperCase(), path: rawPath },
|
|
14335
|
+
...guarantees.length ? { guarantees } : {},
|
|
14336
|
+
...effect ? { effect } : {},
|
|
14337
|
+
...ext ? { ext } : {}
|
|
14338
|
+
});
|
|
14339
|
+
}
|
|
14340
|
+
}
|
|
14341
|
+
const types = [];
|
|
14342
|
+
const schemas = parsed.components?.schemas ?? {};
|
|
14343
|
+
for (const [id, schema] of Object.entries(schemas)) {
|
|
14344
|
+
const props = schema.properties ?? {};
|
|
14345
|
+
const required = new Set(schema.required ?? []);
|
|
14346
|
+
types.push({
|
|
14347
|
+
id,
|
|
14348
|
+
name: typeof schema.title === "string" ? schema.title : id,
|
|
14349
|
+
kind: "value-object",
|
|
14350
|
+
fields: Object.entries(props).map(([fname, fschema]) => ({
|
|
14351
|
+
name: fname,
|
|
14352
|
+
type: typeRefFromSchema(fschema),
|
|
14353
|
+
...required.has(fname) ? {} : { optional: true }
|
|
14354
|
+
}))
|
|
14355
|
+
});
|
|
14356
|
+
}
|
|
14357
|
+
const securitySchemes = parsed.components?.securitySchemes ?? {};
|
|
14358
|
+
const firstScheme = Object.values(securitySchemes)[0];
|
|
14359
|
+
const importedAuth = firstScheme ? authFromSecurityScheme(firstScheme) : void 0;
|
|
14360
|
+
const entry = {
|
|
14361
|
+
id: `${projectName}-api`,
|
|
14362
|
+
name: typeof info.title === "string" ? info.title : projectName,
|
|
14363
|
+
audience: "external",
|
|
14364
|
+
type: "REST",
|
|
14365
|
+
component: `${projectName}-api`,
|
|
14366
|
+
methods,
|
|
14367
|
+
details: typeof info.description === "string" ? info.description : `Imported OpenAPI surface of ${projectName}.`,
|
|
14368
|
+
...typeof info.version === "string" ? { version: info.version } : {},
|
|
14369
|
+
...importedAuth ? { auth: importedAuth } : {}
|
|
14370
|
+
};
|
|
14371
|
+
return SurfaceSnapshotSchema.parse({
|
|
14372
|
+
projectName,
|
|
14373
|
+
origin: "authored",
|
|
14374
|
+
...typeof info.version === "string" ? { version: info.version } : {},
|
|
14375
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14376
|
+
interfaces: [entry],
|
|
14377
|
+
types
|
|
14378
|
+
});
|
|
14379
|
+
}
|
|
14380
|
+
var yaml2, PRIMITIVES;
|
|
14381
|
+
var init_openapi = __esm({
|
|
14382
|
+
"src/core/openapi.ts"() {
|
|
14383
|
+
"use strict";
|
|
14384
|
+
yaml2 = __toESM(require("js-yaml"));
|
|
14385
|
+
init_models();
|
|
14386
|
+
PRIMITIVES = {
|
|
14387
|
+
string: { type: "string" },
|
|
14388
|
+
number: { type: "number" },
|
|
14389
|
+
float: { type: "number" },
|
|
14390
|
+
decimal: { type: "number" },
|
|
14391
|
+
int: { type: "integer" },
|
|
14392
|
+
integer: { type: "integer" },
|
|
14393
|
+
boolean: { type: "boolean" },
|
|
14394
|
+
bool: { type: "boolean" },
|
|
14395
|
+
date: { type: "string", format: "date-time" },
|
|
14396
|
+
datetime: { type: "string", format: "date-time" },
|
|
14397
|
+
uuid: { type: "string", format: "uuid" },
|
|
14398
|
+
json: { type: "object" },
|
|
14399
|
+
object: { type: "object" },
|
|
14400
|
+
any: {},
|
|
14401
|
+
unknown: {},
|
|
14402
|
+
void: {}
|
|
14403
|
+
};
|
|
14404
|
+
}
|
|
14405
|
+
});
|
|
14406
|
+
|
|
14407
|
+
// src/core/surfaces.ts
|
|
14408
|
+
function surfacesDir(rootDir) {
|
|
14409
|
+
return path16.join(rootDir, ".wai", SURFACES_DIRNAME);
|
|
14410
|
+
}
|
|
14411
|
+
function audienceRank(audience) {
|
|
14412
|
+
const idx = SURFACE_AUDIENCES.indexOf(audience ?? "instance");
|
|
14413
|
+
return idx === -1 ? SURFACE_AUDIENCES.indexOf("instance") : idx;
|
|
14414
|
+
}
|
|
14415
|
+
function stateIdString() {
|
|
14416
|
+
const s = computeStateId();
|
|
14417
|
+
return `${s.algorithm}:${s.digest}`;
|
|
14418
|
+
}
|
|
14419
|
+
function computeTypeClosure(entries, types) {
|
|
14420
|
+
const included = /* @__PURE__ */ new Map();
|
|
14421
|
+
const queue = [];
|
|
14422
|
+
const enqueueRef = (ref) => {
|
|
14423
|
+
if (BUILTIN_TYPES.has(ref.toLowerCase())) return;
|
|
14424
|
+
for (const spec of types) {
|
|
14425
|
+
const qualifiedId2 = spec.subsystem && !spec.id.startsWith(`${spec.subsystem}::`) ? `${spec.subsystem}::${spec.id}` : spec.id;
|
|
14426
|
+
if (matchTypeRef(ref, qualifiedId2) && !included.has(spec.id)) {
|
|
14427
|
+
included.set(spec.id, spec);
|
|
14428
|
+
queue.push(spec.id);
|
|
14429
|
+
}
|
|
14663
14430
|
}
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
var tag = b.getAttribute('data-openapi-tag') || '';
|
|
14669
|
-
if (typeof opts !== 'undefined' && opts && opts.onOpenApi) { opts.onOpenApi(tag); return; }
|
|
14670
|
-
var href = openApiSiblingHref();
|
|
14671
|
-
if (href) window.open(href + (tag ? '#/' + tag : ''), '_blank', 'noopener');
|
|
14672
|
-
});
|
|
14673
|
-
})(oapis[oi]);
|
|
14431
|
+
};
|
|
14432
|
+
for (const entry of entries) {
|
|
14433
|
+
for (const m of entry.methods) {
|
|
14434
|
+
for (const ref of methodTypeRefs(m)) enqueueRef(ref);
|
|
14674
14435
|
}
|
|
14675
|
-
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
opts.onOpenSpec(b.getAttribute('data-openspec-kind'), b.getAttribute('data-openspec-id') || '');
|
|
14681
|
-
}
|
|
14682
|
-
});
|
|
14683
|
-
})(ospecs[si]);
|
|
14436
|
+
}
|
|
14437
|
+
while (queue.length) {
|
|
14438
|
+
const spec = included.get(queue.shift());
|
|
14439
|
+
for (const field of spec.fields) {
|
|
14440
|
+
for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
|
|
14684
14441
|
}
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14442
|
+
}
|
|
14443
|
+
return [...included.values()].map((t) => ({
|
|
14444
|
+
id: t.id,
|
|
14445
|
+
name: t.name,
|
|
14446
|
+
kind: t.kind,
|
|
14447
|
+
fields: t.fields.map((f) => ({
|
|
14448
|
+
name: f.name,
|
|
14449
|
+
type: f.type,
|
|
14450
|
+
...f.description ? { description: f.description } : {},
|
|
14451
|
+
...f.optional ? { optional: true } : {}
|
|
14452
|
+
}))
|
|
14453
|
+
}));
|
|
14454
|
+
}
|
|
14455
|
+
function projectOwnSurface(maxAudience) {
|
|
14456
|
+
const system = loadSystemSpec();
|
|
14457
|
+
if (!system) {
|
|
14458
|
+
throw new Error("Cannot project a surface: the L0 system spec is missing.");
|
|
14459
|
+
}
|
|
14460
|
+
const subsystems = loadSubsystemSpecs();
|
|
14461
|
+
const components = loadComponentSpecs();
|
|
14462
|
+
const interfaces = loadInterfaceSpecs();
|
|
14463
|
+
const types = loadTypeSpecs();
|
|
14464
|
+
const floor = audienceRank(maxAudience);
|
|
14465
|
+
const rawEntries = system.publicInterfaces ?? [];
|
|
14466
|
+
const entries = [];
|
|
14467
|
+
for (const raw of rawEntries) {
|
|
14468
|
+
const audience = raw.audience ?? "instance";
|
|
14469
|
+
if (audienceRank(audience) < floor) continue;
|
|
14470
|
+
if (!raw.component) continue;
|
|
14471
|
+
const comp = components.find((c) => c.id === raw.component);
|
|
14472
|
+
if (!comp) continue;
|
|
14473
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!raw.interface || i.id === raw.interface));
|
|
14474
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
14475
|
+
const subsystemType = subsystems.find((s) => s.id === comp.subsystem)?.publicInterfaces.find((pi) => pi.component === comp.id)?.type;
|
|
14476
|
+
entries.push({
|
|
14477
|
+
id: raw.id ?? raw.interface ?? comp.id,
|
|
14478
|
+
name: raw.name ?? comp.name,
|
|
14479
|
+
audience,
|
|
14480
|
+
type: raw.type ?? subsystemType ?? "Custom",
|
|
14481
|
+
component: comp.id,
|
|
14482
|
+
methods,
|
|
14483
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
14484
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
14485
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
14486
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
14487
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
14488
|
+
details: raw.details ?? "",
|
|
14489
|
+
...raw.version ? { version: raw.version } : {},
|
|
14490
|
+
...raw.stability ? { stability: raw.stability } : {}
|
|
14491
|
+
});
|
|
14492
|
+
}
|
|
14493
|
+
return SurfaceSnapshotSchema.parse({
|
|
14494
|
+
projectName: system.name,
|
|
14495
|
+
origin: "generated",
|
|
14496
|
+
stateId: stateIdString(),
|
|
14497
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14498
|
+
interfaces: entries,
|
|
14499
|
+
types: computeTypeClosure(entries, types)
|
|
14500
|
+
});
|
|
14501
|
+
}
|
|
14502
|
+
function projectChildSurface() {
|
|
14503
|
+
return projectOwnSurface("project");
|
|
14504
|
+
}
|
|
14505
|
+
function localName(id) {
|
|
14506
|
+
return id.split("::").pop();
|
|
14507
|
+
}
|
|
14508
|
+
function projectSubsystemSurface(subsystemId) {
|
|
14509
|
+
const system = loadSystemSpec();
|
|
14510
|
+
if (!system) {
|
|
14511
|
+
throw new Error("Cannot project a subsystem surface: the L0 system spec is missing.");
|
|
14512
|
+
}
|
|
14513
|
+
const subsystems = loadSubsystemSpecs();
|
|
14514
|
+
const target = subsystems.find((s) => s.id === subsystemId);
|
|
14515
|
+
if (!target) {
|
|
14516
|
+
throw new Error(`Cannot project a subsystem surface: subsystem "${subsystemId}" does not exist.`);
|
|
14517
|
+
}
|
|
14518
|
+
const components = loadComponentSpecs();
|
|
14519
|
+
const interfaces = loadInterfaceSpecs();
|
|
14520
|
+
const types = loadTypeSpecs();
|
|
14521
|
+
const entries = [];
|
|
14522
|
+
const unprojectable = [];
|
|
14523
|
+
for (const pub of target.publicInterfaces ?? []) {
|
|
14524
|
+
if (!pub.component) continue;
|
|
14525
|
+
const comp = components.find((c) => c.id === pub.component || c.id === `${subsystemId}::${pub.component}`);
|
|
14526
|
+
if (!comp) continue;
|
|
14527
|
+
if (!CROSS_BOUNDARY_TARGETS.has(comp.componentType)) {
|
|
14528
|
+
unprojectable.push({ component: pub.component, componentType: comp.componentType });
|
|
14529
|
+
continue;
|
|
14693
14530
|
}
|
|
14531
|
+
const compInterfaces = interfaces.filter((i) => i.component === comp.id && (!pub.interface || i.id === pub.interface || i.id === `${subsystemId}::${pub.interface}`));
|
|
14532
|
+
const methods = compInterfaces.flatMap((i) => i.methods);
|
|
14533
|
+
entries.push({
|
|
14534
|
+
id: localName(pub.interface ?? comp.id),
|
|
14535
|
+
name: comp.name,
|
|
14536
|
+
// Family ceiling: a sibling surface is consumable by the system family only.
|
|
14537
|
+
audience: "project",
|
|
14538
|
+
type: pub.type ?? "Custom",
|
|
14539
|
+
// The snapshot carries the LOCAL portal name — consumers resolve cross-tree
|
|
14540
|
+
// refs by their final segment.
|
|
14541
|
+
component: localName(comp.id),
|
|
14542
|
+
methods,
|
|
14543
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
14544
|
+
// Project the backing component's auth + basePath so the codec can emit
|
|
14545
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
14546
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
14547
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
14548
|
+
details: pub.details ?? ""
|
|
14549
|
+
});
|
|
14694
14550
|
}
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14551
|
+
for (const skipped of unprojectable) {
|
|
14552
|
+
console.error(
|
|
14553
|
+
`[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).`
|
|
14554
|
+
);
|
|
14555
|
+
}
|
|
14556
|
+
return SurfaceSnapshotSchema.parse({
|
|
14557
|
+
projectName: `${system.name}::${subsystemId}`,
|
|
14558
|
+
origin: "generated",
|
|
14559
|
+
stateId: stateIdString(),
|
|
14560
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14561
|
+
interfaces: entries,
|
|
14562
|
+
types: computeTypeClosure(entries, types)
|
|
14563
|
+
});
|
|
14564
|
+
}
|
|
14565
|
+
function listSnapshots(rootDir = getProjectRoot()) {
|
|
14566
|
+
const dir = surfacesDir(rootDir);
|
|
14567
|
+
if (!fs12.existsSync(dir)) return [];
|
|
14568
|
+
const out = [];
|
|
14569
|
+
for (const file of fs12.readdirSync(dir)) {
|
|
14570
|
+
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
|
|
14571
|
+
try {
|
|
14572
|
+
out.push(SurfaceSnapshotSchema.parse(readYamlFile(path16.join(dir, file))));
|
|
14573
|
+
} catch {
|
|
14709
14574
|
}
|
|
14710
|
-
|
|
14711
|
-
|
|
14575
|
+
}
|
|
14576
|
+
return out;
|
|
14577
|
+
}
|
|
14578
|
+
function snapshotFilename(projectName) {
|
|
14579
|
+
return `${safeFilenamePart(projectName)}.yaml`;
|
|
14580
|
+
}
|
|
14581
|
+
function writeSnapshotIfChanged(snapshot, rootDir) {
|
|
14582
|
+
const dir = surfacesDir(rootDir);
|
|
14583
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
14584
|
+
const p = path16.join(dir, snapshotFilename(snapshot.projectName));
|
|
14585
|
+
const next = SurfaceSnapshotSchema.parse(snapshot);
|
|
14586
|
+
if (fs12.existsSync(p)) {
|
|
14587
|
+
try {
|
|
14588
|
+
const existing = SurfaceSnapshotSchema.parse(readYamlFile(p));
|
|
14589
|
+
if (surfaceContentKey(existing) === surfaceContentKey(next)) {
|
|
14590
|
+
return { path: p, changed: false };
|
|
14591
|
+
}
|
|
14592
|
+
} catch {
|
|
14712
14593
|
}
|
|
14713
|
-
})();
|
|
14714
|
-
})();
|
|
14715
|
-
</script>
|
|
14716
|
-
</body>
|
|
14717
|
-
</html>
|
|
14718
|
-
`;
|
|
14719
14594
|
}
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
// src/core/rules/repository.ts
|
|
14723
|
-
function addRule(rule) {
|
|
14724
|
-
ruleSet.push(rule);
|
|
14595
|
+
writeYamlFile(p, next);
|
|
14596
|
+
return { path: p, changed: true };
|
|
14725
14597
|
}
|
|
14726
|
-
function
|
|
14727
|
-
|
|
14728
|
-
for (const rule of SDD_RULES) addRule(rule);
|
|
14598
|
+
function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
14599
|
+
return writeSnapshotIfChanged(snapshot, rootDir).path;
|
|
14729
14600
|
}
|
|
14730
|
-
function
|
|
14731
|
-
|
|
14601
|
+
function loadSurfaceSnapshots() {
|
|
14602
|
+
return listSnapshots();
|
|
14732
14603
|
}
|
|
14733
|
-
function
|
|
14734
|
-
const
|
|
14735
|
-
|
|
14604
|
+
function listMountSnapshots(mounts) {
|
|
14605
|
+
const bound = path16.resolve(getProjectRoot());
|
|
14606
|
+
const out = [];
|
|
14607
|
+
for (const namespace of mounts) {
|
|
14608
|
+
const dir = resolveSubprojectForNamespace(namespace);
|
|
14609
|
+
if (!dir || path16.resolve(dir) === bound) continue;
|
|
14610
|
+
const snapshots = listSnapshots(dir);
|
|
14611
|
+
if (snapshots.length > 0) out.push({ namespace, snapshots });
|
|
14612
|
+
}
|
|
14613
|
+
return out;
|
|
14736
14614
|
}
|
|
14737
|
-
function
|
|
14738
|
-
return
|
|
14615
|
+
function loadMountSurfaceSnapshots(mounts) {
|
|
14616
|
+
return listMountSnapshots(mounts);
|
|
14739
14617
|
}
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14618
|
+
function selectPortalSpec(renderedSet, portalId) {
|
|
14619
|
+
const hit = renderedSet.find((spec) => spec.portalId === portalId);
|
|
14620
|
+
if (!hit) {
|
|
14621
|
+
const known = renderedSet.map((s) => s.portalId).join(", ");
|
|
14622
|
+
throw new Error(`Unknown portal "${portalId}" \u2014 this surface renders: ${known || "(no portals)"}.`);
|
|
14623
|
+
}
|
|
14624
|
+
return [hit];
|
|
14625
|
+
}
|
|
14626
|
+
function perPortalPath(resolvedOut, portalId) {
|
|
14627
|
+
const ext = path16.extname(resolvedOut);
|
|
14628
|
+
const stem2 = ext ? resolvedOut.slice(0, -ext.length) : resolvedOut;
|
|
14629
|
+
return `${stem2}.${safeFilenamePart(portalId)}${ext}`;
|
|
14630
|
+
}
|
|
14631
|
+
function writeSurfaceFile(outPath, snapshot, renderedSet) {
|
|
14632
|
+
const resolved = path16.resolve(outPath);
|
|
14633
|
+
fs12.mkdirSync(path16.dirname(resolved), { recursive: true });
|
|
14634
|
+
if (!renderedSet || renderedSet.length === 0) {
|
|
14635
|
+
writeYamlFile(resolved, snapshot);
|
|
14636
|
+
return [resolved];
|
|
14637
|
+
}
|
|
14638
|
+
if (renderedSet.length === 1) {
|
|
14639
|
+
fs12.writeFileSync(resolved, renderedSet[0].document);
|
|
14640
|
+
return [resolved];
|
|
14641
|
+
}
|
|
14642
|
+
return renderedSet.map((spec) => {
|
|
14643
|
+
const target = perPortalPath(resolved, spec.portalId);
|
|
14644
|
+
fs12.writeFileSync(target, spec.document);
|
|
14645
|
+
return target;
|
|
14646
|
+
});
|
|
14647
|
+
}
|
|
14648
|
+
function exportResult(snapshot, renderedSet, writtenPaths) {
|
|
14649
|
+
const rendered = renderedSet?.length === 1 ? renderedSet[0].document : void 0;
|
|
14650
|
+
return {
|
|
14651
|
+
snapshot,
|
|
14652
|
+
...rendered !== void 0 ? { rendered } : {},
|
|
14653
|
+
...renderedSet ? { renderedSet } : {},
|
|
14654
|
+
...writtenPaths.length === 1 ? { writtenTo: writtenPaths[0] } : {},
|
|
14655
|
+
...writtenPaths.length ? { writtenPaths } : {}
|
|
14656
|
+
};
|
|
14657
|
+
}
|
|
14658
|
+
function exportSurface(maxAudience, format, outPath, portalId) {
|
|
14659
|
+
const snapshot = projectOwnSurface(maxAudience);
|
|
14660
|
+
let renderedSet = format === "openapi" ? toOpenApiSet(snapshot) : void 0;
|
|
14661
|
+
if (renderedSet && portalId) renderedSet = selectPortalSpec(renderedSet, portalId);
|
|
14662
|
+
const writtenPaths = outPath ? writeSurfaceFile(outPath, snapshot, renderedSet) : [];
|
|
14663
|
+
return exportResult(snapshot, renderedSet, writtenPaths);
|
|
14664
|
+
}
|
|
14665
|
+
function importSurface(sourcePath, origin) {
|
|
14666
|
+
const resolved = path16.resolve(sourcePath);
|
|
14667
|
+
if (!fs12.existsSync(resolved)) {
|
|
14668
|
+
throw new Error(`Surface document not found: ${resolved}`);
|
|
14669
|
+
}
|
|
14670
|
+
const body = fs12.readFileSync(resolved, "utf8");
|
|
14671
|
+
let snapshot;
|
|
14672
|
+
if (isOpenApiDocument(body)) {
|
|
14673
|
+
const projectName = path16.basename(resolved).replace(/\.(json|ya?ml)$/i, "");
|
|
14674
|
+
snapshot = fromOpenApi(body, projectName);
|
|
14675
|
+
snapshot = { ...snapshot, origin };
|
|
14676
|
+
} else {
|
|
14677
|
+
snapshot = SurfaceSnapshotSchema.parse(readYamlFile(resolved));
|
|
14678
|
+
snapshot = { ...snapshot, origin };
|
|
14679
|
+
}
|
|
14680
|
+
saveSnapshot(snapshot);
|
|
14681
|
+
return snapshot;
|
|
14682
|
+
}
|
|
14683
|
+
function pinFamilySurfaces() {
|
|
14684
|
+
const parent = resolveChainingParent();
|
|
14685
|
+
if (!parent) return null;
|
|
14686
|
+
const childRoot = getProjectRoot();
|
|
14687
|
+
const projected = runWithProjectRoot(parent.parentRoot, () => {
|
|
14688
|
+
invalidateSpecCache();
|
|
14689
|
+
const siblings = loadSubsystemSpecs().filter((s) => !s.id.includes("::") && s.id !== parent.subsystemId);
|
|
14690
|
+
return [projectChildSurface(), ...siblings.map((s) => projectSubsystemSurface(s.id))];
|
|
14691
|
+
});
|
|
14692
|
+
const before = new Map(listSnapshots(childRoot).map((s) => [s.projectName, surfaceContentKey(s)]));
|
|
14693
|
+
const changed = [];
|
|
14694
|
+
for (const snapshot of projected) {
|
|
14695
|
+
const stored = saveSnapshot(snapshot, childRoot);
|
|
14696
|
+
if (before.get(snapshot.projectName) !== surfaceContentKey(SurfaceSnapshotSchema.parse(snapshot))) {
|
|
14697
|
+
changed.push(stored);
|
|
14698
|
+
}
|
|
14699
|
+
}
|
|
14700
|
+
return changed;
|
|
14701
|
+
}
|
|
14702
|
+
function computeParentStateId(parentRoot) {
|
|
14703
|
+
return computeStateIdAt(parentRoot);
|
|
14704
|
+
}
|
|
14705
|
+
function listExternalInterfaces() {
|
|
14706
|
+
const snapshots = listSnapshots();
|
|
14707
|
+
const chainingParent = resolveChainingParent();
|
|
14708
|
+
const parentStateId = chainingParent ? computeParentStateId(chainingParent.parentRoot) : null;
|
|
14709
|
+
return snapshots.map((snapshot) => {
|
|
14710
|
+
const generated = snapshot.origin === "generated";
|
|
14711
|
+
const sourceKind = !generated ? "foreign" : snapshot.projectName.includes("::") ? "sibling" : "parent";
|
|
14712
|
+
const freshness = generated && parentStateId ? snapshot.stateId === parentStateId ? "fresh" : "stale" : "unverifiable";
|
|
14713
|
+
return {
|
|
14714
|
+
projectName: snapshot.projectName,
|
|
14715
|
+
origin: snapshot.origin,
|
|
14716
|
+
sourceKind,
|
|
14717
|
+
generatedAt: snapshot.generatedAt,
|
|
14718
|
+
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
14719
|
+
...snapshot.version ? { version: snapshot.version } : {},
|
|
14720
|
+
freshness,
|
|
14721
|
+
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
14722
|
+
};
|
|
14723
|
+
});
|
|
14724
|
+
}
|
|
14725
|
+
function surfaceContentKey(snapshot) {
|
|
14726
|
+
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
14727
|
+
return JSON.stringify(content);
|
|
14728
|
+
}
|
|
14729
|
+
var fs12, path16, SURFACES_DIRNAME, CROSS_BOUNDARY_TARGETS;
|
|
14730
|
+
var init_surfaces = __esm({
|
|
14731
|
+
"src/core/surfaces.ts"() {
|
|
14743
14732
|
"use strict";
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14733
|
+
fs12 = __toESM(require("fs"));
|
|
14734
|
+
path16 = __toESM(require("path"));
|
|
14735
|
+
init_fs();
|
|
14736
|
+
init_yaml();
|
|
14737
|
+
init_filenames();
|
|
14738
|
+
init_models();
|
|
14739
|
+
init_specs2();
|
|
14740
|
+
init_statehash();
|
|
14741
|
+
init_type_analysis();
|
|
14742
|
+
init_openapi();
|
|
14743
|
+
SURFACES_DIRNAME = "surfaces";
|
|
14744
|
+
CROSS_BOUNDARY_TARGETS = /* @__PURE__ */ new Set(["Portal", "Gateway", "Observer"]);
|
|
14747
14745
|
}
|
|
14748
14746
|
});
|
|
14749
14747
|
|
|
@@ -14956,7 +14954,8 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
14956
14954
|
let recursive = true;
|
|
14957
14955
|
let extensions;
|
|
14958
14956
|
let treatAllAsComplete = false;
|
|
14959
|
-
|
|
14957
|
+
let crossTree;
|
|
14958
|
+
if (rulesOrOptions && ("scopeSubsystem" in rulesOrOptions || "recursive" in rulesOrOptions || "rules" in rulesOrOptions || "projectType" in rulesOrOptions || "extensions" in rulesOrOptions || "treatAllAsComplete" in rulesOrOptions || "crossTree" in rulesOrOptions)) {
|
|
14960
14959
|
const opts = rulesOrOptions;
|
|
14961
14960
|
rules = opts.rules;
|
|
14962
14961
|
projectType = opts.projectType ?? "backend";
|
|
@@ -14964,6 +14963,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
14964
14963
|
recursive = opts.recursive ?? true;
|
|
14965
14964
|
extensions = opts.extensions;
|
|
14966
14965
|
treatAllAsComplete = opts.treatAllAsComplete ?? false;
|
|
14966
|
+
crossTree = opts.crossTree;
|
|
14967
14967
|
}
|
|
14968
14968
|
extensions ??= loadProjectExtensions();
|
|
14969
14969
|
scanAllSpecs({ recursive });
|
|
@@ -15015,6 +15015,9 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
15015
15015
|
for (const err of extensions.errors) {
|
|
15016
15016
|
issues.push(issue("error", "EXTENSION_LOAD_ERROR", err));
|
|
15017
15017
|
}
|
|
15018
|
+
const mountSurfaceSnapshots = loadMountSurfaceSnapshots(
|
|
15019
|
+
subsystems.filter((s) => s.projectPath).map((s) => s.id)
|
|
15020
|
+
);
|
|
15018
15021
|
const ctx = buildRuleContext({
|
|
15019
15022
|
system,
|
|
15020
15023
|
subsystems,
|
|
@@ -15030,6 +15033,7 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
15030
15033
|
// By-name selections only: a legacy path ref pins nothing to check.
|
|
15031
15034
|
packSelections: projectPackSelections(),
|
|
15032
15035
|
surfaceSnapshots,
|
|
15036
|
+
mountSurfaceSnapshots,
|
|
15033
15037
|
codeModel,
|
|
15034
15038
|
issues
|
|
15035
15039
|
});
|
|
@@ -15038,56 +15042,26 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
15038
15042
|
for (const rule of ruleSequence()) {
|
|
15039
15043
|
rule.check(ctx);
|
|
15040
15044
|
}
|
|
15041
|
-
const
|
|
15042
|
-
|
|
15043
|
-
);
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
unverified++;
|
|
15062
|
-
continue;
|
|
15063
|
-
}
|
|
15064
|
-
if (SUBPROJECT_CONFORMANCE_CODES.has(iss.code)) {
|
|
15065
|
-
if (iss.severity === "error") {
|
|
15066
|
-
iss.severity = "warning";
|
|
15067
|
-
downgraded++;
|
|
15068
|
-
}
|
|
15069
|
-
iss.crossTreeContext = true;
|
|
15070
|
-
}
|
|
15071
|
-
}
|
|
15072
|
-
if (unverified > 0 || downgraded > 0) {
|
|
15073
|
-
const notes = [];
|
|
15074
|
-
if (unverified > 0) {
|
|
15075
|
-
notes.push(
|
|
15076
|
-
`${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.`
|
|
15077
|
-
);
|
|
15078
|
-
}
|
|
15079
|
-
if (downgraded > 0) {
|
|
15080
|
-
notes.push(
|
|
15081
|
-
`${downgraded} code\u2194spec conformance finding(s) (parent-root-relative source paths) were downgraded to warnings.`
|
|
15082
|
-
);
|
|
15083
|
-
}
|
|
15084
|
-
issues.unshift({
|
|
15085
|
-
severity: "warning",
|
|
15086
|
-
code: "CHAINED_SUBPROJECT_CONTEXT",
|
|
15087
|
-
crossTreeContext: true,
|
|
15088
|
-
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.`
|
|
15089
|
-
});
|
|
15090
|
-
}
|
|
15045
|
+
const uncovered = (i) => SUBPROJECT_REFERENCE_CODES.has(i.code) && !i.surfaceResolved;
|
|
15046
|
+
const chainingParent = crossTree !== "off" && issues.some(uncovered) ? findChainingParent(getProjectRoot()) : null;
|
|
15047
|
+
const resolution = chainingParent ? resolveThroughParent(getProjectRoot(), treatAllAsComplete) : null;
|
|
15048
|
+
if (resolution) {
|
|
15049
|
+
const parentSeverity = /* @__PURE__ */ new Map();
|
|
15050
|
+
for (const i of resolution.issues) {
|
|
15051
|
+
const key = `${i.code}|${i.specId ?? ""}`;
|
|
15052
|
+
if (i.severity === "error" || !parentSeverity.has(key)) parentSeverity.set(key, i.severity);
|
|
15053
|
+
}
|
|
15054
|
+
const rejudged = (i) => {
|
|
15055
|
+
const judged = parentSeverity.get(`${i.code}|${i.specId ?? ""}`);
|
|
15056
|
+
return judged === "error" || judged === "warning" && i.severity === "warning";
|
|
15057
|
+
};
|
|
15058
|
+
const kept = issues.filter((i) => !uncovered(i) && !(i.surfaceResolved && rejudged(i)));
|
|
15059
|
+
const merged = dedupeIssues([...kept, ...resolution.issues]);
|
|
15060
|
+
return {
|
|
15061
|
+
valid: merged.every((i) => i.severity !== "error"),
|
|
15062
|
+
issues: merged,
|
|
15063
|
+
resolvedThrough: { root: resolution.root, scope: resolution.scope }
|
|
15064
|
+
};
|
|
15091
15065
|
}
|
|
15092
15066
|
return {
|
|
15093
15067
|
valid: issues.every((i) => i.severity !== "error"),
|
|
@@ -15099,6 +15073,74 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
15099
15073
|
});
|
|
15100
15074
|
}
|
|
15101
15075
|
}
|
|
15076
|
+
function resolveThroughParent(boundRoot, treatAllAsComplete) {
|
|
15077
|
+
const reach = getRequestParentReach();
|
|
15078
|
+
if (reach && !reach.parentReach) return null;
|
|
15079
|
+
const ceiling = reach?.topRoot ? path18.resolve(reach.topRoot) : void 0;
|
|
15080
|
+
const chain = [];
|
|
15081
|
+
let top = path18.resolve(boundRoot);
|
|
15082
|
+
while (top !== ceiling) {
|
|
15083
|
+
const hop = findChainingParent(top);
|
|
15084
|
+
if (!hop) break;
|
|
15085
|
+
const next = path18.resolve(hop.parentRoot);
|
|
15086
|
+
if (ceiling && !isWithinOrEqual(ceiling, next)) break;
|
|
15087
|
+
chain.unshift(hop.subsystemId);
|
|
15088
|
+
top = next;
|
|
15089
|
+
}
|
|
15090
|
+
if (chain.length === 0) return null;
|
|
15091
|
+
const scope = chain.join("::");
|
|
15092
|
+
const inner = runWithProjectRoot(top, () => {
|
|
15093
|
+
invalidateSpecCache();
|
|
15094
|
+
let governing = {};
|
|
15095
|
+
try {
|
|
15096
|
+
const config = loadProjectConfig();
|
|
15097
|
+
governing = { rules: config.rules, projectType: config.projectType };
|
|
15098
|
+
} catch {
|
|
15099
|
+
}
|
|
15100
|
+
return validateSddTree({
|
|
15101
|
+
...governing,
|
|
15102
|
+
scopeSubsystem: scope,
|
|
15103
|
+
recursive: true,
|
|
15104
|
+
crossTree: "off",
|
|
15105
|
+
treatAllAsComplete
|
|
15106
|
+
});
|
|
15107
|
+
});
|
|
15108
|
+
if (inner.issues.some((i) => i.code === "MISSING_SYSTEM_SPEC" || i.code === "SUBSYSTEM_NOT_FOUND")) return null;
|
|
15109
|
+
const local = scope.split("::").pop();
|
|
15110
|
+
const issues = inner.issues.map((i) => ({
|
|
15111
|
+
...i,
|
|
15112
|
+
message: stripNamespace(i.message, scope),
|
|
15113
|
+
...i.specId !== void 0 ? { specId: i.specId === scope ? local : stripNamespace(i.specId, scope) } : {}
|
|
15114
|
+
}));
|
|
15115
|
+
return { root: top, scope, issues };
|
|
15116
|
+
}
|
|
15117
|
+
function stripNamespace(text2, scope) {
|
|
15118
|
+
const prefix = `${scope}::`;
|
|
15119
|
+
let out = "";
|
|
15120
|
+
let from = 0;
|
|
15121
|
+
for (let at = text2.indexOf(prefix); at !== -1; at = text2.indexOf(prefix, from)) {
|
|
15122
|
+
const atBoundary = at === 0 || !isIdChar(text2[at - 1]);
|
|
15123
|
+
out += text2.slice(from, at) + (atBoundary ? "" : prefix);
|
|
15124
|
+
from = at + prefix.length;
|
|
15125
|
+
}
|
|
15126
|
+
return out + text2.slice(from);
|
|
15127
|
+
}
|
|
15128
|
+
function isIdChar(ch) {
|
|
15129
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_" || ch === "-" || ch === "." || ch === ":";
|
|
15130
|
+
}
|
|
15131
|
+
function isWithinOrEqual(dir, target) {
|
|
15132
|
+
const rel2 = path18.relative(dir, target);
|
|
15133
|
+
return rel2 === "" || !rel2.startsWith("..") && !path18.isAbsolute(rel2);
|
|
15134
|
+
}
|
|
15135
|
+
function dedupeIssues(list2) {
|
|
15136
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15137
|
+
return list2.filter((i) => {
|
|
15138
|
+
const key = `${i.code}|${i.specId ?? ""}|${i.message}`;
|
|
15139
|
+
if (seen.has(key)) return false;
|
|
15140
|
+
seen.add(key);
|
|
15141
|
+
return true;
|
|
15142
|
+
});
|
|
15143
|
+
}
|
|
15102
15144
|
function settledStatusBearing(loaded) {
|
|
15103
15145
|
let settled;
|
|
15104
15146
|
try {
|
|
@@ -15126,7 +15168,7 @@ function settledStatusBearing(loaded) {
|
|
|
15126
15168
|
function validateAsComplete(options) {
|
|
15127
15169
|
return validateSddTree({ ...options ?? {}, treatAllAsComplete: true });
|
|
15128
15170
|
}
|
|
15129
|
-
var path18, SUBPROJECT_REFERENCE_CODES
|
|
15171
|
+
var path18, SUBPROJECT_REFERENCE_CODES;
|
|
15130
15172
|
var init_validation = __esm({
|
|
15131
15173
|
"src/core/validation.ts"() {
|
|
15132
15174
|
"use strict";
|
|
@@ -15152,16 +15194,6 @@ var init_validation = __esm({
|
|
|
15152
15194
|
"CROSS_SUBSYSTEM_NON_ADAPTER",
|
|
15153
15195
|
"CROSS_TREE_REF_UNRESOLVED"
|
|
15154
15196
|
]);
|
|
15155
|
-
SUBPROJECT_CONFORMANCE_CODES = /* @__PURE__ */ new Set([
|
|
15156
|
-
"MISSING_SOURCE_FILE",
|
|
15157
|
-
"SOURCE_PATH_ESCAPES_ROOT",
|
|
15158
|
-
"MISSING_SOURCE_PATH",
|
|
15159
|
-
"UNREALIZED_METHOD",
|
|
15160
|
-
"CONFORMANCE_ANALYSIS_SKIPPED",
|
|
15161
|
-
"CONFORMANCE_DEGRADED",
|
|
15162
|
-
"UNDECLARED_DEPENDENCY",
|
|
15163
|
-
"UNREALIZED_DEPENDENCY"
|
|
15164
|
-
]);
|
|
15165
15197
|
}
|
|
15166
15198
|
});
|
|
15167
15199
|
|
|
@@ -15991,6 +16023,19 @@ function stripNamespaceFromImplementation(spec, prefix) {
|
|
|
15991
16023
|
}))
|
|
15992
16024
|
};
|
|
15993
16025
|
}
|
|
16026
|
+
function childRelativeFilePaths(spec, authoringRoot, childRoot) {
|
|
16027
|
+
const reexpress = (p) => {
|
|
16028
|
+
if (path19.isAbsolute(p)) return p;
|
|
16029
|
+
const rel2 = path19.relative(childRoot, path19.resolve(authoringRoot, p));
|
|
16030
|
+
const inside = rel2 !== "" && rel2 !== ".." && !rel2.startsWith(`..${path19.sep}`) && !path19.isAbsolute(rel2);
|
|
16031
|
+
return inside ? rel2.replace(/\\/g, "/") : p;
|
|
16032
|
+
};
|
|
16033
|
+
return {
|
|
16034
|
+
...spec,
|
|
16035
|
+
...spec.sourcePath ? { sourcePath: reexpress(spec.sourcePath) } : {},
|
|
16036
|
+
...spec.simPath ? { simPath: reexpress(spec.simPath) } : {}
|
|
16037
|
+
};
|
|
16038
|
+
}
|
|
15994
16039
|
function stripNamespaceFromType(spec, prefix) {
|
|
15995
16040
|
return {
|
|
15996
16041
|
...spec,
|
|
@@ -16309,6 +16354,18 @@ var init_specs2 = __esm({
|
|
|
16309
16354
|
this.lastSignatureCheckMs = Date.now();
|
|
16310
16355
|
return this.cachedIndex;
|
|
16311
16356
|
}
|
|
16357
|
+
/**
|
|
16358
|
+
* The id a scan-time loader issue is anchored to: the refused spec's own id
|
|
16359
|
+
* when the file got far enough to carry one, else its name on disk (the
|
|
16360
|
+
* containing directory in the nested layout, where every file is `.index` /
|
|
16361
|
+
* `.interface`), qualified into the mount's namespace exactly as the loader
|
|
16362
|
+
* qualifies the spec itself — so scoping a validate to a mount keeps it.
|
|
16363
|
+
*/
|
|
16364
|
+
loaderIssueSpecId(file, rawId, namespacePrefix) {
|
|
16365
|
+
const stem2 = path19.basename(file, ".yaml");
|
|
16366
|
+
const local = rawId ?? (stem2.startsWith(".") ? path19.basename(path19.dirname(file)) : stem2);
|
|
16367
|
+
return namespacePrefix ? qualifyId(local, namespacePrefix, this.rootSubsystems) : local;
|
|
16368
|
+
}
|
|
16312
16369
|
scanSpecsForProject(projectDir, namespacePrefix, visitedDirs, maxDepth, currentDepth) {
|
|
16313
16370
|
const index = emptyIndex();
|
|
16314
16371
|
const projectPaths = aiPathsAt(projectDir);
|
|
@@ -16322,6 +16379,7 @@ var init_specs2 = __esm({
|
|
|
16322
16379
|
const normFile = path19.normalize(file);
|
|
16323
16380
|
if (normFile === systemYaml) continue;
|
|
16324
16381
|
let detectedType = "spec";
|
|
16382
|
+
let rawId;
|
|
16325
16383
|
try {
|
|
16326
16384
|
const raw = readYamlFile(file);
|
|
16327
16385
|
if (raw === null || typeof raw !== "object") {
|
|
@@ -16329,10 +16387,12 @@ var init_specs2 = __esm({
|
|
|
16329
16387
|
severity: "error",
|
|
16330
16388
|
code: "INVALID_YAML",
|
|
16331
16389
|
message: `Spec file "${file}" is not a valid YAML object or is empty.`,
|
|
16332
|
-
specId:
|
|
16390
|
+
specId: this.loaderIssueSpecId(file, void 0, namespacePrefix)
|
|
16333
16391
|
});
|
|
16334
16392
|
continue;
|
|
16335
16393
|
}
|
|
16394
|
+
const idField = raw.id;
|
|
16395
|
+
if (typeof idField === "string" && idField) rawId = idField;
|
|
16336
16396
|
if ("parentSystem" in raw) {
|
|
16337
16397
|
detectedType = "subsystem";
|
|
16338
16398
|
const parsed = SubsystemSpecSchema.parse(raw);
|
|
@@ -16384,16 +16444,15 @@ var init_specs2 = __esm({
|
|
|
16384
16444
|
severity: "error",
|
|
16385
16445
|
code: "UNKNOWN_SPEC_TYPE",
|
|
16386
16446
|
message: `Spec file "${file}" does not match any recognized L1-L4 schema structure.`,
|
|
16387
|
-
specId:
|
|
16447
|
+
specId: this.loaderIssueSpecId(file, rawId, namespacePrefix)
|
|
16388
16448
|
});
|
|
16389
16449
|
}
|
|
16390
16450
|
} catch (e) {
|
|
16391
|
-
const filename = path19.basename(file, ".yaml");
|
|
16392
16451
|
this.loaderIssues.push({
|
|
16393
16452
|
severity: "error",
|
|
16394
16453
|
code: "SCHEMA_VALIDATION_ERROR",
|
|
16395
16454
|
message: `Failed to parse ${detectedType} spec "${file}": ${e.message || String(e)}`,
|
|
16396
|
-
specId:
|
|
16455
|
+
specId: this.loaderIssueSpecId(file, rawId, namespacePrefix)
|
|
16397
16456
|
});
|
|
16398
16457
|
}
|
|
16399
16458
|
}
|
|
@@ -16875,7 +16934,11 @@ var init_specs2 = __esm({
|
|
|
16875
16934
|
}
|
|
16876
16935
|
prepareImplementationForWrite(spec) {
|
|
16877
16936
|
const prefix = this.writePrefixFor(spec.id);
|
|
16878
|
-
|
|
16937
|
+
if (!prefix) return spec;
|
|
16938
|
+
const stripped = stripNamespaceFromImplementation(spec, prefix);
|
|
16939
|
+
const mount = this.getSubprojectPrefix(spec.id);
|
|
16940
|
+
const childRoot = mount ? this.resolveSubprojectForNamespace(mount) : null;
|
|
16941
|
+
return childRoot ? childRelativeFilePaths(stripped, this.rootDir, childRoot) : stripped;
|
|
16879
16942
|
}
|
|
16880
16943
|
prepareTypeForWrite(spec) {
|
|
16881
16944
|
const prefix = this.writePrefixFor(spec.id);
|
|
@@ -18174,6 +18237,7 @@ function externalizeSubsystem(subsystemId, projectPath) {
|
|
|
18174
18237
|
});
|
|
18175
18238
|
ensureDir(path20.dirname(childFooDir));
|
|
18176
18239
|
fs14.renameSync(fooDir, childFooDir);
|
|
18240
|
+
rebaseImplementationPaths(childFooDir, parentRoot, childDir);
|
|
18177
18241
|
patchSubsystemIndex(path20.join(childFooDir, ".index.yaml"), (s) => {
|
|
18178
18242
|
s.parentSystem = childSystemName;
|
|
18179
18243
|
delete s.projectPath;
|
|
@@ -18224,6 +18288,7 @@ function internalizeSubsystem(subsystemId) {
|
|
|
18224
18288
|
fs14.rmSync(fooDir, { recursive: true, force: true });
|
|
18225
18289
|
ensureDir(path20.dirname(fooDir));
|
|
18226
18290
|
fs14.renameSync(childFooDir, fooDir);
|
|
18291
|
+
rebaseImplementationPaths(fooDir, childDir, parentRoot);
|
|
18227
18292
|
patchSubsystemIndex(path20.join(fooDir, ".index.yaml"), (s) => {
|
|
18228
18293
|
s.parentSystem = parentSystemName;
|
|
18229
18294
|
delete s.projectPath;
|
|
@@ -18324,6 +18389,28 @@ function rewriteRefsInDir(specsDir, renameMap, excludeDir) {
|
|
|
18324
18389
|
if (changed) writeYamlFile(file, raw);
|
|
18325
18390
|
}
|
|
18326
18391
|
}
|
|
18392
|
+
function rebaseImplementationPaths(specsDir, fromRoot, toRoot) {
|
|
18393
|
+
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
18394
|
+
let raw;
|
|
18395
|
+
try {
|
|
18396
|
+
raw = readYamlFile(file);
|
|
18397
|
+
} catch {
|
|
18398
|
+
continue;
|
|
18399
|
+
}
|
|
18400
|
+
if (!raw || typeof raw !== "object" || !("contract" in raw)) continue;
|
|
18401
|
+
let changed = false;
|
|
18402
|
+
for (const key of ["sourcePath", "simPath"]) {
|
|
18403
|
+
const p = raw[key];
|
|
18404
|
+
if (typeof p !== "string" || p === "" || path20.isAbsolute(p)) continue;
|
|
18405
|
+
const next = toPosixPath(path20.relative(toRoot, path20.resolve(fromRoot, p)));
|
|
18406
|
+
if (next !== p) {
|
|
18407
|
+
raw[key] = next;
|
|
18408
|
+
changed = true;
|
|
18409
|
+
}
|
|
18410
|
+
}
|
|
18411
|
+
if (changed) writeYamlFile(file, raw);
|
|
18412
|
+
}
|
|
18413
|
+
}
|
|
18327
18414
|
function patchSubsystemIndex(indexPath, mutate) {
|
|
18328
18415
|
if (!fs14.existsSync(indexPath)) return;
|
|
18329
18416
|
const raw = readYamlFile(indexPath);
|
|
@@ -23578,7 +23665,7 @@ NOTICE:
|
|
|
23578
23665
|
name: import_zod10.z.string().describe("Human-readable implementation name"),
|
|
23579
23666
|
description: import_zod10.z.string().describe("Implementation details"),
|
|
23580
23667
|
contract: import_zod10.z.string().describe("The L3 Interface contract ID this implements"),
|
|
23581
|
-
sourcePath: import_zod10.z.string().optional().describe("Optional: target source code file path relative to project root"),
|
|
23668
|
+
sourcePath: import_zod10.z.string().optional().describe("Optional: target source code file path relative to project root \u2014 for a chained subproject's implementation (qualified id), relative to that subproject's root; a path given relative to this root that lands inside the subproject is re-expressed for you"),
|
|
23582
23669
|
simPath: import_zod10.z.string().optional().describe("Optional: the committed integration-sim harness file (project-relative; N:1 sharing allowed). The validator proves it exists and its import graph wires the REAL modules (this component + each direct dependency; technology adapters may stay faked) \u2014 running it is CI's job. Declaring the first simPath in a subsystem activates MISSING_INTEGRATION_SIM for its other complete non-leaf implementations"),
|
|
23583
23670
|
technologies: import_zod10.z.array(import_zod10.z.string()).optional().describe(`External technologies this implementation binds to (e.g. ["mysql"]) \u2014 declares this component's ownership tree as the technology's home; references outside it are flagged (TECH_LEAKAGE) and contract identifiers must stay intent-language. Only for Adapter/Store/Registry/Index components.`),
|
|
23584
23671
|
detail: detailEnum.optional().describe("Spec-level narrative detail default for all methods"),
|
|
@@ -23767,7 +23854,8 @@ NOTICE:
|
|
|
23767
23854
|
return json({
|
|
23768
23855
|
valid: result.valid,
|
|
23769
23856
|
errors: result.issues.filter((i) => i.severity === "error"),
|
|
23770
|
-
warnings: result.issues.filter((i) => i.severity === "warning")
|
|
23857
|
+
warnings: result.issues.filter((i) => i.severity === "warning"),
|
|
23858
|
+
...result.resolvedThrough ? { resolvedThrough: result.resolvedThrough } : {}
|
|
23771
23859
|
});
|
|
23772
23860
|
} catch (e) {
|
|
23773
23861
|
return errText(String(e));
|
|
@@ -25189,19 +25277,19 @@ function commitStagedTree(stagingDir, destDir, roots, backupExisting) {
|
|
|
25189
25277
|
const backupDir = path28.join(root, BACKUP_DIR, timestampSlug());
|
|
25190
25278
|
let backedUp = false;
|
|
25191
25279
|
for (const rootRel of roots) {
|
|
25192
|
-
const
|
|
25193
|
-
const stagedSpecDir = path28.join(stagingDir,
|
|
25280
|
+
const relative15 = rootRel === "." ? "" : rootRel;
|
|
25281
|
+
const stagedSpecDir = path28.join(stagingDir, relative15, ".wai");
|
|
25194
25282
|
if (!fs18.existsSync(stagedSpecDir)) continue;
|
|
25195
|
-
const liveSpecDir = liveSpecDirFor(path28.join(root,
|
|
25283
|
+
const liveSpecDir = liveSpecDirFor(path28.join(root, relative15));
|
|
25196
25284
|
if (backupExisting && liveSpecDir && fs18.existsSync(liveSpecDir)) {
|
|
25197
|
-
const backupTarget = path28.join(backupDir,
|
|
25285
|
+
const backupTarget = path28.join(backupDir, relative15, path28.basename(liveSpecDir));
|
|
25198
25286
|
fs18.mkdirSync(path28.dirname(backupTarget), { recursive: true });
|
|
25199
25287
|
fs18.renameSync(liveSpecDir, backupTarget);
|
|
25200
25288
|
backedUp = true;
|
|
25201
25289
|
} else if (liveSpecDir && fs18.existsSync(liveSpecDir)) {
|
|
25202
25290
|
fs18.rmSync(liveSpecDir, { recursive: true, force: true });
|
|
25203
25291
|
}
|
|
25204
|
-
const target = path28.join(root,
|
|
25292
|
+
const target = path28.join(root, relative15, ".wai");
|
|
25205
25293
|
fs18.mkdirSync(path28.dirname(target), { recursive: true });
|
|
25206
25294
|
if (fs18.existsSync(target)) fs18.rmSync(target, { recursive: true, force: true });
|
|
25207
25295
|
fs18.renameSync(stagedSpecDir, target);
|
|
@@ -26168,7 +26256,6 @@ function isCiDraftWaivable(issue2) {
|
|
|
26168
26256
|
if (issue2.severity !== "warning") return false;
|
|
26169
26257
|
if (issue2.code === "DRAFT_COMPONENT_WARNING") return true;
|
|
26170
26258
|
if (issue2.code === "UNUSED_COMPONENT") return issue2.draftContext === true;
|
|
26171
|
-
if (issue2.crossTreeContext === true) return true;
|
|
26172
26259
|
return false;
|
|
26173
26260
|
}
|
|
26174
26261
|
async function runValidate(options = {}) {
|
|
@@ -26233,6 +26320,11 @@ async function runValidate(options = {}) {
|
|
|
26233
26320
|
scopeSubsystem: options.subsystem,
|
|
26234
26321
|
recursive: options.recursive ?? true
|
|
26235
26322
|
});
|
|
26323
|
+
if (sddResult.resolvedThrough) {
|
|
26324
|
+
logger.info(
|
|
26325
|
+
`Chained subproject \u2014 verified through the parent project at ${sddResult.resolvedThrough.root} (mount "${sddResult.resolvedThrough.scope}").`
|
|
26326
|
+
);
|
|
26327
|
+
}
|
|
26236
26328
|
if (sddResult.issues.length === 0) {
|
|
26237
26329
|
logger.success("Spec tree is valid and component type boundaries are enforced.");
|
|
26238
26330
|
} else {
|
|
@@ -26427,14 +26519,14 @@ var http = __toESM(require("http"));
|
|
|
26427
26519
|
var https = __toESM(require("https"));
|
|
26428
26520
|
init_defaults();
|
|
26429
26521
|
function downloadFile(url, dest) {
|
|
26430
|
-
return new Promise((
|
|
26522
|
+
return new Promise((resolve30, reject) => {
|
|
26431
26523
|
const file = fs22.createWriteStream(dest);
|
|
26432
26524
|
const get4 = url.startsWith("https://") ? https.get : http.get;
|
|
26433
26525
|
get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
26434
26526
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
26435
26527
|
file.close();
|
|
26436
26528
|
res.destroy();
|
|
26437
|
-
downloadFile(res.headers.location, dest).then(
|
|
26529
|
+
downloadFile(res.headers.location, dest).then(resolve30).catch(reject);
|
|
26438
26530
|
return;
|
|
26439
26531
|
}
|
|
26440
26532
|
if (res.statusCode !== 200) {
|
|
@@ -26446,7 +26538,7 @@ function downloadFile(url, dest) {
|
|
|
26446
26538
|
res.pipe(file);
|
|
26447
26539
|
file.on("finish", () => {
|
|
26448
26540
|
res.destroy();
|
|
26449
|
-
file.close(() =>
|
|
26541
|
+
file.close(() => resolve30());
|
|
26450
26542
|
});
|
|
26451
26543
|
file.on("error", (err) => {
|
|
26452
26544
|
res.destroy();
|
|
@@ -26623,7 +26715,7 @@ function releaseChannelLabel(tag) {
|
|
|
26623
26715
|
return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
|
|
26624
26716
|
}
|
|
26625
26717
|
function fetchReleases(repo) {
|
|
26626
|
-
return new Promise((
|
|
26718
|
+
return new Promise((resolve30, reject) => {
|
|
26627
26719
|
const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
|
|
26628
26720
|
const options = {
|
|
26629
26721
|
headers: {
|
|
@@ -26643,7 +26735,7 @@ function fetchReleases(repo) {
|
|
|
26643
26735
|
return;
|
|
26644
26736
|
}
|
|
26645
26737
|
try {
|
|
26646
|
-
|
|
26738
|
+
resolve30(JSON.parse(data));
|
|
26647
26739
|
} catch {
|
|
26648
26740
|
reject(new Error("Failed to parse GitHub API response"));
|
|
26649
26741
|
}
|
|
@@ -26818,7 +26910,7 @@ async function filteredCheckbox(config) {
|
|
|
26818
26910
|
32,
|
|
26819
26911
|
Math.max(...items.map((i) => i.label.length))
|
|
26820
26912
|
);
|
|
26821
|
-
return new Promise((
|
|
26913
|
+
return new Promise((resolve30) => {
|
|
26822
26914
|
const checked = /* @__PURE__ */ new Set();
|
|
26823
26915
|
let cursor = 0;
|
|
26824
26916
|
let filterIdx = 0;
|
|
@@ -26885,7 +26977,7 @@ async function filteredCheckbox(config) {
|
|
|
26885
26977
|
);
|
|
26886
26978
|
teardown();
|
|
26887
26979
|
const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
|
|
26888
|
-
|
|
26980
|
+
resolve30(result);
|
|
26889
26981
|
}
|
|
26890
26982
|
function abort() {
|
|
26891
26983
|
process.stdout.write("\n");
|
|
@@ -35085,7 +35177,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
|
|
|
35085
35177
|
let current2 = req;
|
|
35086
35178
|
while (current2.status === "pending" && Date.now() < deadline) {
|
|
35087
35179
|
const remaining = deadline - Date.now();
|
|
35088
|
-
await new Promise((
|
|
35180
|
+
await new Promise((resolve30) => setTimeout(resolve30, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
|
|
35089
35181
|
expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
|
|
35090
35182
|
current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
|
|
35091
35183
|
}
|
|
@@ -36312,7 +36404,7 @@ var ShareSnapshotIndex = class {
|
|
|
36312
36404
|
function putSnapshot(dataDir, snapshot) {
|
|
36313
36405
|
return new ShareSnapshotRegistry(new ShareSnapshotStore(dataDir)).put(snapshot);
|
|
36314
36406
|
}
|
|
36315
|
-
function
|
|
36407
|
+
function getSnapshot(dataDir, snapshotId) {
|
|
36316
36408
|
return new ShareSnapshotIndex(new ShareSnapshotStore(dataDir)).get(snapshotId);
|
|
36317
36409
|
}
|
|
36318
36410
|
function getSnapshotArtifact(dataDir, snapshotId, kind, portalId) {
|
|
@@ -40043,7 +40135,9 @@ async function handleMcpRequest(cfg, req, res, body, credential) {
|
|
|
40043
40135
|
});
|
|
40044
40136
|
return;
|
|
40045
40137
|
}
|
|
40046
|
-
|
|
40138
|
+
const topRoot = existingProjectRoot(cfg.dataDir, binding.projectId) ?? binding.rootPath;
|
|
40139
|
+
const parentReach = principal.projects.includes("*") || principal.projects.includes(binding.projectId);
|
|
40140
|
+
await runWithProjectBinding(binding.rootPath, { topRoot, parentReach }, async () => {
|
|
40047
40141
|
const projectId = binding.projectId;
|
|
40048
40142
|
const subproject = binding.subproject;
|
|
40049
40143
|
const confinementError = subprojectConfinementError(projectId, subproject, body);
|
|
@@ -40134,7 +40228,7 @@ function resolveSharedView(cfg, token, meta) {
|
|
|
40134
40228
|
record(cfg.dataDir, link?.id ?? "", meta, check.outcome);
|
|
40135
40229
|
return { found: false, outcome: check.outcome };
|
|
40136
40230
|
}
|
|
40137
|
-
const snapshot =
|
|
40231
|
+
const snapshot = getSnapshot(cfg.dataDir, check.link.snapshotId);
|
|
40138
40232
|
record(cfg.dataDir, check.link.id, meta, "served");
|
|
40139
40233
|
return {
|
|
40140
40234
|
found: true,
|
|
@@ -41343,11 +41437,11 @@ async function runServe(options = {}) {
|
|
|
41343
41437
|
logger.info(` data dir: ${import_chalk17.default.gray(cfg.dataDir)}`);
|
|
41344
41438
|
logger.blank();
|
|
41345
41439
|
logger.info("Press Ctrl+C to stop.");
|
|
41346
|
-
await new Promise((
|
|
41440
|
+
await new Promise((resolve30) => {
|
|
41347
41441
|
const shutdown = () => {
|
|
41348
41442
|
logger.info("Shutting down\u2026");
|
|
41349
41443
|
handle.close();
|
|
41350
|
-
|
|
41444
|
+
resolve30();
|
|
41351
41445
|
};
|
|
41352
41446
|
process.on("SIGINT", shutdown);
|
|
41353
41447
|
process.on("SIGTERM", shutdown);
|
|
@@ -41412,11 +41506,11 @@ async function runDev(options = {}) {
|
|
|
41412
41506
|
logger.blank();
|
|
41413
41507
|
logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
|
|
41414
41508
|
if (options.open) openBrowser(url);
|
|
41415
|
-
await new Promise((
|
|
41509
|
+
await new Promise((resolve30) => {
|
|
41416
41510
|
const shutdown = () => {
|
|
41417
41511
|
logger.info("Shutting down\u2026");
|
|
41418
41512
|
handle.close();
|
|
41419
|
-
|
|
41513
|
+
resolve30();
|
|
41420
41514
|
};
|
|
41421
41515
|
process.on("SIGINT", shutdown);
|
|
41422
41516
|
process.on("SIGTERM", shutdown);
|
|
@@ -41967,16 +42061,6 @@ async function runSurface(action, options = {}) {
|
|
|
41967
42061
|
}
|
|
41968
42062
|
return;
|
|
41969
42063
|
}
|
|
41970
|
-
case "generate-children": {
|
|
41971
|
-
const written = generateChildSnapshots();
|
|
41972
|
-
if (!written.length) {
|
|
41973
|
-
logger.info("Delivered surfaces are already up to date \u2014 nothing rewritten.");
|
|
41974
|
-
return;
|
|
41975
|
-
}
|
|
41976
|
-
logger.success(`Updated ${written.length} delivered surface(s):`);
|
|
41977
|
-
for (const p of written) logger.info(` ${p}`);
|
|
41978
|
-
return;
|
|
41979
|
-
}
|
|
41980
42064
|
case "externals": {
|
|
41981
42065
|
const entries = listExternalInterfaces();
|
|
41982
42066
|
if (!entries.length) {
|
|
@@ -41991,8 +42075,22 @@ async function runSurface(action, options = {}) {
|
|
|
41991
42075
|
}
|
|
41992
42076
|
return;
|
|
41993
42077
|
}
|
|
42078
|
+
case "pin": {
|
|
42079
|
+
const written = pinFamilySurfaces();
|
|
42080
|
+
if (written === null) {
|
|
42081
|
+
logger.info("This project is not a chained subproject \u2014 there is no parent family to pin.");
|
|
42082
|
+
return;
|
|
42083
|
+
}
|
|
42084
|
+
if (!written.length) {
|
|
42085
|
+
logger.info("Pinned family surfaces are already up to date \u2014 nothing rewritten.");
|
|
42086
|
+
return;
|
|
42087
|
+
}
|
|
42088
|
+
logger.success(`Pinned ${written.length} family surface(s) from the parent:`);
|
|
42089
|
+
for (const p of written) logger.info(` ${p}`);
|
|
42090
|
+
return;
|
|
42091
|
+
}
|
|
41994
42092
|
default:
|
|
41995
|
-
throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list,
|
|
42093
|
+
throw new WaironError(`Unknown surface action "${action}" (supported: export, import, list, externals, pin).`);
|
|
41996
42094
|
}
|
|
41997
42095
|
}
|
|
41998
42096
|
|
|
@@ -42732,12 +42830,6 @@ async function runLock2(options) {
|
|
|
42732
42830
|
logger.info("Cancelled. Nothing was changed.");
|
|
42733
42831
|
return;
|
|
42734
42832
|
}
|
|
42735
|
-
const childPaths = generateChildSnapshots();
|
|
42736
|
-
if (childPaths.length > 0) {
|
|
42737
|
-
logger.blank();
|
|
42738
|
-
logger.success(`Updated ${childPaths.length} delivered surface(s) in the chained children:`);
|
|
42739
|
-
for (const p of childPaths) logger.info(` ${p}`);
|
|
42740
|
-
}
|
|
42741
42833
|
logger.blank();
|
|
42742
42834
|
await runGenerate({ domain: options.subsystem });
|
|
42743
42835
|
logger.blank();
|
|
@@ -43000,7 +43092,7 @@ mcpCmd.command("status").description("Show whether the wairon MCP server is regi
|
|
|
43000
43092
|
program.command("produce <target>").description("project the local project's specs to a producer target (notion | miro)").option("--page <id>", "parent page/board id in the target").option("--token <token>", "integration token (else env, else interactive prompt)").action(async (target, opts) => {
|
|
43001
43093
|
await runProduce(target, { page: opts.page, token: opts.token });
|
|
43002
43094
|
});
|
|
43003
|
-
program.command("surface <action>").description("public surface exchange: export | import | list |
|
|
43095
|
+
program.command("surface <action>").description("public surface exchange: export | import | list | externals | pin").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
43004
43096
|
await runSurface(action, {
|
|
43005
43097
|
audience: opts.audience,
|
|
43006
43098
|
format: opts.format,
|