@wairon/cli 5.1.1-dev.79 → 5.1.1-dev.80
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 +862 -209
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +789 -136
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +2 -2
- package/dist/templates/variants/logic-shapes.yaml +2 -2
- 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.80";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -616,7 +616,40 @@ function declaredPackNames(config) {
|
|
|
616
616
|
function declaredProfileIds(config) {
|
|
617
617
|
return [...new Set(config.profileSelection?.profileIds ?? [])];
|
|
618
618
|
}
|
|
619
|
-
|
|
619
|
+
function slugifyProjectName(name) {
|
|
620
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z0-9]+/, "").replace(/[^a-z0-9]+$/, "");
|
|
621
|
+
return slug === "" ? null : slug;
|
|
622
|
+
}
|
|
623
|
+
function effectiveProjectId(config) {
|
|
624
|
+
if (config.id !== void 0) return config.id;
|
|
625
|
+
return slugifyProjectName(config.name);
|
|
626
|
+
}
|
|
627
|
+
function projectIdentity(config, lockedProjectId) {
|
|
628
|
+
const id = effectiveProjectId(config);
|
|
629
|
+
const source = config.id !== void 0 ? "declared" : id !== null ? "defaulted" : "none";
|
|
630
|
+
const problems = [];
|
|
631
|
+
if (source === "defaulted") {
|
|
632
|
+
problems.push({ kind: "defaulted", detail: `no id is declared; the name "${config.name}" yields "${id}"` });
|
|
633
|
+
} else if (source === "none") {
|
|
634
|
+
problems.push({ kind: "ambiguous", detail: `no id is declared, and the name "${config.name}" yields no id` });
|
|
635
|
+
} else if (!PROJECT_ID_RE.test(config.id)) {
|
|
636
|
+
problems.push({ kind: "ambiguous", detail: `the declared id "${config.id}" breaks the project-id grammar` });
|
|
637
|
+
}
|
|
638
|
+
if (lockedProjectId !== void 0 && id !== lockedProjectId) {
|
|
639
|
+
problems.push({
|
|
640
|
+
kind: "changed",
|
|
641
|
+
detail: id === null ? `the lock approved "${lockedProjectId}", and the project now has no id` : `the lock approved "${lockedProjectId}", and the project now resolves to "${id}"`
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
...id !== null ? { id } : {},
|
|
646
|
+
source,
|
|
647
|
+
name: config.name,
|
|
648
|
+
...lockedProjectId !== void 0 ? { lockedId: lockedProjectId } : {},
|
|
649
|
+
problems
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
var path5, import_zod3, BuiltinTargetConfigSchema, CustomTargetConfigSchema, TargetConfigSchema, NamingRuleConfigSchema, DocumentationRuleConfigSchema, ComplexityRuleConfigSchema, CarriedDebtKindSchema, CarriedFindingSchema, CarriedDebtSchema, ConformanceRuleConfigSchema, DesignDepthSchema, RulesConfigSchema, PathsConfigSchema, PackSelectionSchema, ProfileSelectionSubjectSchema, ProjectProfileSelectionSchema, ProjectConfigSchema, PACK_EXT_RE, PROJECT_ID_RE;
|
|
620
653
|
var init_project = __esm({
|
|
621
654
|
"src/models/project.ts"() {
|
|
622
655
|
"use strict";
|
|
@@ -938,7 +971,16 @@ var init_project = __esm({
|
|
|
938
971
|
* CLI versions.
|
|
939
972
|
*/
|
|
940
973
|
schemaVersion: import_zod3.z.string().default("1.0.0"),
|
|
941
|
-
/**
|
|
974
|
+
/**
|
|
975
|
+
* The project's stable identity: a slug of [a-z0-9-_.] that starts and ends
|
|
976
|
+
* alphanumeric (PROJECT_ID_RE). Optional in the file: a project that declares
|
|
977
|
+
* none answers to its name slugified (effectiveProjectId) until a deliberate
|
|
978
|
+
* writer sets one — an ordinary save never writes the default. Deliberately a plain
|
|
979
|
+
* string here rather than the grammar: a malformed id must be REPORTED
|
|
980
|
+
* (PROJECT_ID_AMBIGUOUS), not make the whole configuration unreadable.
|
|
981
|
+
*/
|
|
982
|
+
id: import_zod3.z.string().optional(),
|
|
983
|
+
/** Human-readable display name. Not an identity — `id` is. */
|
|
942
984
|
name: import_zod3.z.string(),
|
|
943
985
|
/**
|
|
944
986
|
* The type/profile of the project, which configures targeted guidelines, rules,
|
|
@@ -1032,6 +1074,7 @@ var init_project = __esm({
|
|
|
1032
1074
|
updatedAt: import_zod3.z.string().datetime()
|
|
1033
1075
|
});
|
|
1034
1076
|
PACK_EXT_RE = /\.(ya?ml|cjs|js)$/i;
|
|
1077
|
+
PROJECT_ID_RE = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
|
|
1035
1078
|
}
|
|
1036
1079
|
});
|
|
1037
1080
|
|
|
@@ -1319,18 +1362,19 @@ function registryOver(store, root) {
|
|
|
1319
1362
|
if (!config) throw new ProjectNotInitializedError();
|
|
1320
1363
|
return config;
|
|
1321
1364
|
};
|
|
1365
|
+
const save4 = (config) => store.write(config);
|
|
1322
1366
|
return {
|
|
1323
1367
|
create(config) {
|
|
1324
1368
|
if (store.exists()) {
|
|
1325
1369
|
throw new WaironError(`A project configuration already exists at ${root}; creating one never overwrites it.`);
|
|
1326
1370
|
}
|
|
1327
|
-
|
|
1371
|
+
save4(config);
|
|
1328
1372
|
},
|
|
1329
1373
|
upsertPackSelection(selection) {
|
|
1330
1374
|
const config = current2();
|
|
1331
1375
|
const existing = packsOf(config);
|
|
1332
1376
|
const without = existing.filter((entry) => typeof entry === "string" || entry.name !== selection.name);
|
|
1333
|
-
|
|
1377
|
+
save4(withPacks(config, [...without, selection]));
|
|
1334
1378
|
return without.length !== existing.length;
|
|
1335
1379
|
},
|
|
1336
1380
|
removePackSelection(packName) {
|
|
@@ -1338,24 +1382,24 @@ function registryOver(store, root) {
|
|
|
1338
1382
|
const existing = packsOf(config);
|
|
1339
1383
|
const remaining = existing.filter((entry) => typeof entry === "string" || entry.name !== packName);
|
|
1340
1384
|
if (remaining.length === existing.length) return false;
|
|
1341
|
-
|
|
1385
|
+
save4(withPacks(config, remaining));
|
|
1342
1386
|
return true;
|
|
1343
1387
|
},
|
|
1344
1388
|
setProjectType(projectType) {
|
|
1345
|
-
|
|
1389
|
+
save4({ ...current2(), projectType });
|
|
1346
1390
|
},
|
|
1347
1391
|
recordProfileSelection(selection) {
|
|
1348
|
-
|
|
1392
|
+
save4({ ...current2(), profileSelection: selection });
|
|
1349
1393
|
},
|
|
1350
1394
|
setExecutionTier(tier) {
|
|
1351
1395
|
const config = current2();
|
|
1352
|
-
|
|
1396
|
+
save4({ ...config, execution: { ...config.execution, tier } });
|
|
1353
1397
|
},
|
|
1354
1398
|
registerPackRef(ref) {
|
|
1355
1399
|
const config = current2();
|
|
1356
1400
|
const packs = packsOf(config);
|
|
1357
1401
|
if (packs.includes(ref)) return false;
|
|
1358
|
-
|
|
1402
|
+
save4(withPacks(config, [...packs, ref]));
|
|
1359
1403
|
return true;
|
|
1360
1404
|
},
|
|
1361
1405
|
deregisterPackRef(ref) {
|
|
@@ -1363,16 +1407,16 @@ function registryOver(store, root) {
|
|
|
1363
1407
|
const packs = packsOf(config);
|
|
1364
1408
|
const remaining = packs.filter((entry) => entry !== ref);
|
|
1365
1409
|
if (remaining.length === packs.length) return false;
|
|
1366
|
-
|
|
1410
|
+
save4(withPacks(config, remaining));
|
|
1367
1411
|
return true;
|
|
1368
1412
|
},
|
|
1369
1413
|
markSelectionsBundled(bundled) {
|
|
1370
1414
|
const config = current2();
|
|
1371
|
-
|
|
1415
|
+
save4(withPacks(config, bundleInPlace(packsOf(config), bundled)));
|
|
1372
1416
|
},
|
|
1373
1417
|
pinGlobalPacksAsSelections(selections) {
|
|
1374
1418
|
const config = current2();
|
|
1375
|
-
|
|
1419
|
+
save4({
|
|
1376
1420
|
...config,
|
|
1377
1421
|
extensions: { ...config.extensions, packs: [...packsOf(config), ...selections], useGlobalPacks: false }
|
|
1378
1422
|
});
|
|
@@ -1906,6 +1950,9 @@ function splitNamespace(qualifiedId2) {
|
|
|
1906
1950
|
const localId = parts.pop();
|
|
1907
1951
|
return { prefix: parts.join("::"), localId };
|
|
1908
1952
|
}
|
|
1953
|
+
function isOwnComponentEntry(pi) {
|
|
1954
|
+
return pi.from === void 0 && pi.typeDef === void 0;
|
|
1955
|
+
}
|
|
1909
1956
|
function isDraftSubsystem(subsystem) {
|
|
1910
1957
|
return subsystem.status === "draft" || subsystem.status === "design";
|
|
1911
1958
|
}
|
|
@@ -2019,7 +2066,7 @@ function typeSourceFiles(type) {
|
|
|
2019
2066
|
for (const method2 of type.methods ?? []) add(method2.sourcePath);
|
|
2020
2067
|
return files;
|
|
2021
2068
|
}
|
|
2022
|
-
var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
2069
|
+
var import_zod8, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, RETIRED_STEREOTYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, PortalMountSchema, ComponentSpecSchema, BLOCK_NOUNS, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceTypeExportSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
2023
2070
|
var init_specs = __esm({
|
|
2024
2071
|
"src/models/specs.ts"() {
|
|
2025
2072
|
"use strict";
|
|
@@ -2055,15 +2102,21 @@ var init_specs = __esm({
|
|
|
2055
2102
|
SURFACE_AUDIENCES = ["project", "department", "instance", "partner", "external"];
|
|
2056
2103
|
SurfaceAudienceSchema = import_zod8.z.enum(SURFACE_AUDIENCES);
|
|
2057
2104
|
SystemPublicInterfaceSchema = import_zod8.z.object({
|
|
2058
|
-
/**
|
|
2105
|
+
/** Legacy public name, read as `as` when `as` is absent. */
|
|
2059
2106
|
id: import_zod8.z.string().optional(),
|
|
2060
2107
|
name: import_zod8.z.string().optional(),
|
|
2061
|
-
/**
|
|
2108
|
+
/** Legacy source subsystem, read as `from` when `from` is absent. */
|
|
2062
2109
|
subsystem: import_zod8.z.string().optional(),
|
|
2063
|
-
/**
|
|
2110
|
+
/** The component re-exported one to one; without it and without `typeDef`, a wildcard. */
|
|
2064
2111
|
component: import_zod8.z.string().optional(),
|
|
2065
|
-
/**
|
|
2112
|
+
/** The L3 interface the export narrows to. */
|
|
2066
2113
|
interface: import_zod8.z.string().optional(),
|
|
2114
|
+
/** The source subsystem the entry re-exports from. */
|
|
2115
|
+
from: import_zod8.z.string().optional(),
|
|
2116
|
+
/** A type exported as is, giving it a public name. */
|
|
2117
|
+
typeDef: import_zod8.z.string().optional(),
|
|
2118
|
+
/** The public name: defaults to `id`, then `interface`, then `component` or `typeDef`. */
|
|
2119
|
+
as: import_zod8.z.string().optional(),
|
|
2067
2120
|
/** Surface kind: REST, GraphQL, MessageBus, RPC, or Custom. */
|
|
2068
2121
|
type: import_zod8.z.string().optional(),
|
|
2069
2122
|
details: import_zod8.z.string().optional(),
|
|
@@ -2104,18 +2157,29 @@ var init_specs = __esm({
|
|
|
2104
2157
|
});
|
|
2105
2158
|
PublicInterfaceTypeSchema = import_zod8.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]);
|
|
2106
2159
|
PublicInterfaceSchema = import_zod8.z.object({
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
/**
|
|
2160
|
+
/** Transport kind of an own item; a re-export inherits its target's. */
|
|
2161
|
+
type: PublicInterfaceTypeSchema.optional(),
|
|
2162
|
+
/** Consumer-facing description of an own item; a re-export inherits its target's. */
|
|
2163
|
+
details: import_zod8.z.string().optional(),
|
|
2164
|
+
/** For an own item, the L2 component that realizes it; for a re-export, the component re-exported from `from`. */
|
|
2110
2165
|
component: SpecIdSchema.optional(),
|
|
2111
|
-
/** Optional L3 interface
|
|
2166
|
+
/** Optional L3 interface the entry narrows to. */
|
|
2112
2167
|
interface: SpecIdSchema.optional(),
|
|
2168
|
+
/** Re-export source: another subsystem of the same project. */
|
|
2169
|
+
from: SpecIdSchema.optional(),
|
|
2170
|
+
/** A type exported as is: owned by this subsystem, or re-exported from `from`. */
|
|
2171
|
+
typeDef: SpecIdSchema.optional(),
|
|
2172
|
+
/** The public name; defaults to the narrowed interface, then the component or type id. */
|
|
2173
|
+
as: import_zod8.z.string().optional(),
|
|
2113
2174
|
/**
|
|
2114
2175
|
* The subsystems this surface is published to. Absent, anyone may depend on
|
|
2115
2176
|
* it; present, only these subsystems may — when every entry publishing the
|
|
2116
2177
|
* component names consumers, the union of those lists is the whole set.
|
|
2117
2178
|
*/
|
|
2118
2179
|
consumers: import_zod8.z.array(SpecIdSchema).optional()
|
|
2180
|
+
}).refine((pi) => pi.from !== void 0 || pi.typeDef !== void 0 || pi.type !== void 0 && pi.details !== void 0, {
|
|
2181
|
+
message: "An own component entry states its `type` and `details`; only a re-export (`from`) or a type export (`typeDef`) may leave them out.",
|
|
2182
|
+
path: ["type"]
|
|
2119
2183
|
});
|
|
2120
2184
|
TrustedLinkSchema = import_zod8.z.object({
|
|
2121
2185
|
/** The peer subsystem id this link sanctions tight coupling with. */
|
|
@@ -2392,8 +2456,8 @@ var init_specs = __esm({
|
|
|
2392
2456
|
FindingDeclarationSchema = import_zod8.z.object({
|
|
2393
2457
|
/** UPPER_SNAKE and unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>). */
|
|
2394
2458
|
code: import_zod8.z.string().regex(/^[A-Z][A-Z0-9_]*$/, "Finding code must be UPPER_SNAKE, e.g. UNREALIZED_FINDING"),
|
|
2395
|
-
/** The default before project severity overrides and draft-context downgrades. */
|
|
2396
|
-
severity: import_zod8.z.enum(["error", "warning"]),
|
|
2459
|
+
/** The default before project severity overrides and draft-context downgrades; a notice is reported but never fails the gate. */
|
|
2460
|
+
severity: import_zod8.z.enum(["error", "warning", "notice"]),
|
|
2397
2461
|
/** One line saying what the finding means. */
|
|
2398
2462
|
summary: import_zod8.z.string().min(1, "Finding summary must say what the finding means")
|
|
2399
2463
|
});
|
|
@@ -2865,6 +2929,14 @@ var init_specs = __esm({
|
|
|
2865
2929
|
optional: import_zod8.z.boolean().optional()
|
|
2866
2930
|
})).default([])
|
|
2867
2931
|
});
|
|
2932
|
+
SurfaceTypeExportSchema = import_zod8.z.object({
|
|
2933
|
+
/** The type's public name in the producer's export table. */
|
|
2934
|
+
id: import_zod8.z.string(),
|
|
2935
|
+
/** The id of the type's definition in the snapshot's type closure. */
|
|
2936
|
+
type: import_zod8.z.string(),
|
|
2937
|
+
/** Exposure level from the L0 entry. */
|
|
2938
|
+
audience: import_zod8.z.string().default("instance")
|
|
2939
|
+
});
|
|
2868
2940
|
SurfaceContractEntrySchema = import_zod8.z.object({
|
|
2869
2941
|
id: import_zod8.z.string(),
|
|
2870
2942
|
name: import_zod8.z.string(),
|
|
@@ -2885,7 +2957,11 @@ var init_specs = __esm({
|
|
|
2885
2957
|
* emits it as OpenAPI securitySchemes/security. */
|
|
2886
2958
|
auth: PortalAuthSchema.optional(),
|
|
2887
2959
|
/** The backing Portal's basePath — becomes the per-portal OpenAPI `servers` url. */
|
|
2888
|
-
basePath: import_zod8.z.string().optional()
|
|
2960
|
+
basePath: import_zod8.z.string().optional(),
|
|
2961
|
+
/** Stereotype of the backing component; absent on snapshots written before exports carried it. */
|
|
2962
|
+
componentType: import_zod8.z.string().optional(),
|
|
2963
|
+
/** The L3 interface the export narrows to, when it narrows. */
|
|
2964
|
+
interface: import_zod8.z.string().optional()
|
|
2889
2965
|
});
|
|
2890
2966
|
SurfaceSnapshotSchema = import_zod8.z.object({
|
|
2891
2967
|
/** Producing project/system name — the snapshot's resolution identity. */
|
|
@@ -2897,8 +2973,12 @@ var init_specs = __esm({
|
|
|
2897
2973
|
version: import_zod8.z.string().optional(),
|
|
2898
2974
|
generatedAt: import_zod8.z.string(),
|
|
2899
2975
|
interfaces: import_zod8.z.array(SurfaceContractEntrySchema).default([]),
|
|
2900
|
-
/** Transitive type closure of every exported signature — self-contained. */
|
|
2901
|
-
types: import_zod8.z.array(SurfaceTypeDefSchema).default([])
|
|
2976
|
+
/** Transitive type closure of every exported signature and exported type — self-contained. */
|
|
2977
|
+
types: import_zod8.z.array(SurfaceTypeDefSchema).default([]),
|
|
2978
|
+
/** The producing project's effective id, beside the display-oriented projectName. */
|
|
2979
|
+
projectId: import_zod8.z.string().optional(),
|
|
2980
|
+
/** The types the producer exports by name, listed apart from the contract entries. */
|
|
2981
|
+
exportedTypes: import_zod8.z.array(SurfaceTypeExportSchema).optional()
|
|
2902
2982
|
});
|
|
2903
2983
|
NamedOpenApiSpecSchema = import_zod8.z.object({
|
|
2904
2984
|
portalId: import_zod8.z.string(),
|
|
@@ -3464,6 +3544,18 @@ var init_surface_references = __esm({
|
|
|
3464
3544
|
}
|
|
3465
3545
|
});
|
|
3466
3546
|
|
|
3547
|
+
// src/models/exports.ts
|
|
3548
|
+
function exportTargetKey(e) {
|
|
3549
|
+
return e.kind === "type" ? `type:${e.typeDef}` : `component:${e.component}${e.interface ? `#${e.interface}` : ""}`;
|
|
3550
|
+
}
|
|
3551
|
+
var PUBLIC_NAME_RE;
|
|
3552
|
+
var init_exports = __esm({
|
|
3553
|
+
"src/models/exports.ts"() {
|
|
3554
|
+
"use strict";
|
|
3555
|
+
PUBLIC_NAME_RE = /^[a-z0-9_-]+$/;
|
|
3556
|
+
}
|
|
3557
|
+
});
|
|
3558
|
+
|
|
3467
3559
|
// src/models/index.ts
|
|
3468
3560
|
var init_models = __esm({
|
|
3469
3561
|
"src/models/index.ts"() {
|
|
@@ -3479,6 +3571,7 @@ var init_models = __esm({
|
|
|
3479
3571
|
init_step_config();
|
|
3480
3572
|
init_code_model();
|
|
3481
3573
|
init_surface_references();
|
|
3574
|
+
init_exports();
|
|
3482
3575
|
}
|
|
3483
3576
|
});
|
|
3484
3577
|
|
|
@@ -3493,7 +3586,7 @@ function resolveNarrativeLabels(methodName, steps) {
|
|
|
3493
3586
|
else labels.set(s.label, s.stepNumber);
|
|
3494
3587
|
}
|
|
3495
3588
|
}
|
|
3496
|
-
const
|
|
3589
|
+
const lookup2 = (ref, where) => {
|
|
3497
3590
|
if (typeof ref !== "string" || !ref.length) {
|
|
3498
3591
|
errors.push(`${where} is not a label string`);
|
|
3499
3592
|
return void 0;
|
|
@@ -3507,7 +3600,7 @@ function resolveNarrativeLabels(methodName, steps) {
|
|
|
3507
3600
|
for (const s of steps) {
|
|
3508
3601
|
for (const [labelField, stepField] of SCALAR_REFS) {
|
|
3509
3602
|
if (s[labelField] === void 0) continue;
|
|
3510
|
-
const n =
|
|
3603
|
+
const n = lookup2(s[labelField], `step ${s.stepNumber} ${labelField}`);
|
|
3511
3604
|
if (n !== void 0) {
|
|
3512
3605
|
if (typeof s[stepField] === "number" && s[stepField] !== n) {
|
|
3513
3606
|
errors.push(`step ${s.stepNumber} sets both ${stepField}=${s[stepField]} and ${labelField}="${s[labelField]}" (= step ${n}) \u2014 they disagree`);
|
|
@@ -3523,7 +3616,7 @@ function resolveNarrativeLabels(methodName, steps) {
|
|
|
3523
3616
|
for (const entry of arr) {
|
|
3524
3617
|
if (!entry || typeof entry !== "object" || entry.label === void 0) continue;
|
|
3525
3618
|
const e = entry;
|
|
3526
|
-
const n =
|
|
3619
|
+
const n = lookup2(e.label, `step ${s.stepNumber} ${key} entry`);
|
|
3527
3620
|
if (n !== void 0) {
|
|
3528
3621
|
if (typeof e.step === "number" && e.step !== n) {
|
|
3529
3622
|
errors.push(`step ${s.stepNumber} ${key} entry sets both step=${e.step} and label="${e.label}" (= step ${n}) \u2014 they disagree`);
|
|
@@ -3984,13 +4077,17 @@ function buildCanvasModel(issues = []) {
|
|
|
3984
4077
|
const componentIds = new Set(components.map((c) => c.id));
|
|
3985
4078
|
const publicComponents = /* @__PURE__ */ new Set();
|
|
3986
4079
|
for (const sub of subsystems) {
|
|
3987
|
-
for (const pi of sub.publicInterfaces) {
|
|
4080
|
+
for (const pi of sub.publicInterfaces.filter(isOwnComponentEntry)) {
|
|
3988
4081
|
if (pi.component) publicComponents.add(pi.component);
|
|
3989
4082
|
}
|
|
3990
4083
|
}
|
|
3991
4084
|
const apiTagOf = /* @__PURE__ */ new Map();
|
|
3992
|
-
|
|
3993
|
-
|
|
4085
|
+
if (system) {
|
|
4086
|
+
for (const entry of resolveProjectExports().entries) {
|
|
4087
|
+
if (entry.kind === "component" && entry.component && !apiTagOf.has(entry.component)) {
|
|
4088
|
+
apiTagOf.set(entry.component, entry.publicName);
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
3994
4091
|
}
|
|
3995
4092
|
const patternTypes = /* @__PURE__ */ new Set(["Repository", "FeatureComponent", "RouterComponent"]);
|
|
3996
4093
|
const ownerOf2 = /* @__PURE__ */ new Map();
|
|
@@ -4156,7 +4253,7 @@ function buildCanvasModel(issues = []) {
|
|
|
4156
4253
|
}
|
|
4157
4254
|
const portalOf = /* @__PURE__ */ new Map();
|
|
4158
4255
|
for (const sub of subsystems) {
|
|
4159
|
-
for (const pi of sub.publicInterfaces) {
|
|
4256
|
+
for (const pi of sub.publicInterfaces.filter(isOwnComponentEntry)) {
|
|
4160
4257
|
if (pi.component && !portalOf.has(sub.id)) portalOf.set(sub.id, pi.component);
|
|
4161
4258
|
}
|
|
4162
4259
|
}
|
|
@@ -8811,19 +8908,25 @@ function stateIdString() {
|
|
|
8811
8908
|
const s = computeStateId();
|
|
8812
8909
|
return `${s.algorithm}:${s.digest}`;
|
|
8813
8910
|
}
|
|
8814
|
-
function
|
|
8911
|
+
function qualifiedTypeId2(spec) {
|
|
8912
|
+
return spec.subsystem && !spec.id.startsWith(`${spec.subsystem}::`) ? `${spec.subsystem}::${spec.id}` : spec.id;
|
|
8913
|
+
}
|
|
8914
|
+
function computeTypeClosure(entries, types, exported = []) {
|
|
8815
8915
|
const included = /* @__PURE__ */ new Map();
|
|
8816
8916
|
const queue = [];
|
|
8917
|
+
const include = (spec) => {
|
|
8918
|
+
const key = qualifiedTypeId2(spec);
|
|
8919
|
+
if (included.has(key)) return;
|
|
8920
|
+
included.set(key, spec);
|
|
8921
|
+
queue.push(key);
|
|
8922
|
+
};
|
|
8817
8923
|
const enqueueRef = (ref) => {
|
|
8818
8924
|
if (BUILTIN_TYPES.has(ref.toLowerCase())) return;
|
|
8819
8925
|
for (const spec of types) {
|
|
8820
|
-
|
|
8821
|
-
if (matchTypeRef(ref, qualifiedId2) && !included.has(spec.id)) {
|
|
8822
|
-
included.set(spec.id, spec);
|
|
8823
|
-
queue.push(spec.id);
|
|
8824
|
-
}
|
|
8926
|
+
if (matchTypeRef(ref, qualifiedTypeId2(spec))) include(spec);
|
|
8825
8927
|
}
|
|
8826
8928
|
};
|
|
8929
|
+
for (const spec of exported) include(spec);
|
|
8827
8930
|
for (const entry of entries) {
|
|
8828
8931
|
for (const m of entry.methods) {
|
|
8829
8932
|
for (const ref of methodTypeRefs(m)) enqueueRef(ref);
|
|
@@ -8835,63 +8938,87 @@ function computeTypeClosure(entries, types) {
|
|
|
8835
8938
|
for (const ref of extractTypeIdentifiers(field.type)) enqueueRef(ref);
|
|
8836
8939
|
}
|
|
8837
8940
|
}
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8941
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
8942
|
+
return [...included.entries()].map(([qualified, t]) => {
|
|
8943
|
+
const id = usedIds.has(t.id) ? qualified : t.id;
|
|
8944
|
+
usedIds.add(id);
|
|
8945
|
+
return {
|
|
8946
|
+
id,
|
|
8947
|
+
name: t.name,
|
|
8948
|
+
kind: t.kind,
|
|
8949
|
+
fields: t.fields.map((f) => ({
|
|
8950
|
+
name: f.name,
|
|
8951
|
+
type: f.type,
|
|
8952
|
+
...f.description ? { description: f.description } : {},
|
|
8953
|
+
...f.optional ? { optional: true } : {}
|
|
8954
|
+
}))
|
|
8955
|
+
};
|
|
8956
|
+
});
|
|
8957
|
+
}
|
|
8958
|
+
function boundProjectId() {
|
|
8959
|
+
try {
|
|
8960
|
+
const config = loadProjectConfig();
|
|
8961
|
+
return config ? effectiveProjectId(config) ?? void 0 : void 0;
|
|
8962
|
+
} catch {
|
|
8963
|
+
return void 0;
|
|
8964
|
+
}
|
|
8965
|
+
}
|
|
8966
|
+
function contractEntry(entry, comp, interfaces) {
|
|
8967
|
+
const methods = interfaces.filter((i) => i.component === comp.id && (!entry.interface || i.id === entry.interface)).flatMap((i) => i.methods);
|
|
8968
|
+
return {
|
|
8969
|
+
id: entry.publicName,
|
|
8970
|
+
name: entry.name ?? comp.name,
|
|
8971
|
+
audience: entry.audience ?? "instance",
|
|
8972
|
+
type: entry.type ?? "Custom",
|
|
8973
|
+
component: comp.id,
|
|
8974
|
+
methods,
|
|
8975
|
+
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
8976
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
8977
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
8978
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
8979
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
8980
|
+
details: entry.details ?? "",
|
|
8981
|
+
...entry.version ? { version: entry.version } : {},
|
|
8982
|
+
...entry.stability ? { stability: entry.stability } : {},
|
|
8983
|
+
componentType: comp.componentType,
|
|
8984
|
+
...entry.interface ? { interface: entry.interface } : {}
|
|
8985
|
+
};
|
|
8849
8986
|
}
|
|
8850
8987
|
function projectOwnSurface(maxAudience) {
|
|
8851
8988
|
const system = loadSystemSpec();
|
|
8852
8989
|
if (!system) {
|
|
8853
8990
|
throw new Error("Cannot project a surface: the L0 system spec is missing.");
|
|
8854
8991
|
}
|
|
8855
|
-
const
|
|
8992
|
+
const table = resolveProjectExports();
|
|
8856
8993
|
const components = loadComponentSpecs();
|
|
8857
8994
|
const interfaces = loadInterfaceSpecs();
|
|
8858
8995
|
const types = loadTypeSpecs();
|
|
8859
8996
|
const floor = audienceRank(maxAudience);
|
|
8860
|
-
const
|
|
8997
|
+
const visible = table.entries.filter((e) => audienceRank(e.audience ?? "instance") >= floor);
|
|
8861
8998
|
const entries = [];
|
|
8862
|
-
for (const
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
if (
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
component: comp.id,
|
|
8877
|
-
methods,
|
|
8878
|
-
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
8879
|
-
// Project the backing Portal's auth + basePath so the codec can emit
|
|
8880
|
-
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
8881
|
-
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
8882
|
-
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
8883
|
-
details: raw.details ?? "",
|
|
8884
|
-
...raw.version ? { version: raw.version } : {},
|
|
8885
|
-
...raw.stability ? { stability: raw.stability } : {}
|
|
8886
|
-
});
|
|
8887
|
-
}
|
|
8999
|
+
for (const entry of visible) {
|
|
9000
|
+
if (entry.kind !== "component") continue;
|
|
9001
|
+
const comp = components.find((c) => c.id === entry.component);
|
|
9002
|
+
if (comp) entries.push(contractEntry(entry, comp, interfaces));
|
|
9003
|
+
}
|
|
9004
|
+
const exportedTypes = visible.filter((e) => e.kind === "type");
|
|
9005
|
+
const exportedSpecs = exportedTypes.map((e) => types.find((t) => t.id === e.typeDef && (t.subsystem ?? e.source) === e.source)).filter((t) => !!t);
|
|
9006
|
+
const closure = computeTypeClosure(entries, types, exportedSpecs);
|
|
9007
|
+
const closureIdOf = (e) => {
|
|
9008
|
+
const spec = types.find((t) => t.id === e.typeDef && (t.subsystem ?? e.source) === e.source);
|
|
9009
|
+
if (!spec) return String(e.typeDef);
|
|
9010
|
+
return closure.some((d) => d.id === qualifiedTypeId2(spec)) ? qualifiedTypeId2(spec) : spec.id;
|
|
9011
|
+
};
|
|
9012
|
+
const projectId = boundProjectId();
|
|
8888
9013
|
return SurfaceSnapshotSchema.parse({
|
|
8889
9014
|
projectName: system.name,
|
|
9015
|
+
...projectId ? { projectId } : {},
|
|
8890
9016
|
origin: "generated",
|
|
8891
9017
|
stateId: stateIdString(),
|
|
8892
9018
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8893
9019
|
interfaces: entries,
|
|
8894
|
-
types:
|
|
9020
|
+
types: closure,
|
|
9021
|
+
...exportedTypes.length ? { exportedTypes: exportedTypes.map((e) => ({ id: e.publicName, type: closureIdOf(e), audience: e.audience ?? "instance" })) } : {}
|
|
8895
9022
|
});
|
|
8896
9023
|
}
|
|
8897
9024
|
function projectChildSurface() {
|
|
@@ -9113,7 +9240,8 @@ function listExternalInterfaces() {
|
|
|
9113
9240
|
...snapshot.stateId ? { stateId: snapshot.stateId } : {},
|
|
9114
9241
|
...snapshot.version ? { version: snapshot.version } : {},
|
|
9115
9242
|
freshness,
|
|
9116
|
-
interfaceIds: snapshot.interfaces.map((e) => e.id)
|
|
9243
|
+
interfaceIds: snapshot.interfaces.map((e) => e.id),
|
|
9244
|
+
...snapshot.projectId ? { projectId: snapshot.projectId } : {}
|
|
9117
9245
|
};
|
|
9118
9246
|
});
|
|
9119
9247
|
}
|
|
@@ -10092,18 +10220,18 @@ function buildImportGraph(index, universe) {
|
|
|
10092
10220
|
};
|
|
10093
10221
|
}
|
|
10094
10222
|
function buildOwnershipIndex(ctx) {
|
|
10095
|
-
const
|
|
10223
|
+
const ownedBy2 = /* @__PURE__ */ new Map();
|
|
10096
10224
|
for (const comp of ctx.components) {
|
|
10097
10225
|
if (isRetired(comp) || !isPattern(comp)) continue;
|
|
10098
10226
|
for (const memberId of comp.owns) {
|
|
10099
10227
|
const member = ctx.componentMap.get(memberId);
|
|
10100
10228
|
if (!member || isPattern(member)) continue;
|
|
10101
|
-
if (!
|
|
10229
|
+
if (!ownedBy2.has(memberId)) ownedBy2.set(memberId, comp.id);
|
|
10102
10230
|
}
|
|
10103
10231
|
}
|
|
10104
10232
|
return {
|
|
10105
|
-
ownedMembers: new Set(
|
|
10106
|
-
ownerOf: (memberId) =>
|
|
10233
|
+
ownedMembers: new Set(ownedBy2.keys()),
|
|
10234
|
+
ownerOf: (memberId) => ownedBy2.get(memberId)
|
|
10107
10235
|
};
|
|
10108
10236
|
}
|
|
10109
10237
|
function buildDependencyEdges(ctx) {
|
|
@@ -10323,6 +10451,51 @@ var init_namespace_shadowing = __esm({
|
|
|
10323
10451
|
}
|
|
10324
10452
|
});
|
|
10325
10453
|
|
|
10454
|
+
// src/core/rules/integrity/project-identity.ts
|
|
10455
|
+
var projectIdentityRule;
|
|
10456
|
+
var init_project_identity = __esm({
|
|
10457
|
+
"src/core/rules/integrity/project-identity.ts"() {
|
|
10458
|
+
"use strict";
|
|
10459
|
+
projectIdentityRule = {
|
|
10460
|
+
name: "project-identity",
|
|
10461
|
+
description: "A project is keyed on its id, so the id must be declared, well-formed and stable: a project that declares none answers to one derived from its display name until it writes one (PROJECT_ID_DEFAULTED), a project whose name yields no id or whose declared id breaks the grammar has nothing reliable to key on (PROJECT_ID_AMBIGUOUS), and an id that differs from the one the lock approved has moved under everything that keyed on it (PROJECT_ID_CHANGED).",
|
|
10462
|
+
codes: [
|
|
10463
|
+
{ code: "PROJECT_ID_DEFAULTED", defaultSeverity: "notice", summary: "Project declares no id; it answers to one derived from its display name" },
|
|
10464
|
+
{ code: "PROJECT_ID_AMBIGUOUS", defaultSeverity: "warning", summary: "Project has no usable id: its name yields no slug, or its declared id breaks the grammar" },
|
|
10465
|
+
{ code: "PROJECT_ID_CHANGED", defaultSeverity: "error", summary: "Project id differs from the id the lock approved" }
|
|
10466
|
+
],
|
|
10467
|
+
check(ctx) {
|
|
10468
|
+
const identity = ctx.projectIdentity;
|
|
10469
|
+
if (!identity) return;
|
|
10470
|
+
const problem = (kind) => identity.problems.find((p) => p.kind === kind);
|
|
10471
|
+
if (problem("defaulted")) {
|
|
10472
|
+
ctx.addIssue(
|
|
10473
|
+
"notice",
|
|
10474
|
+
"PROJECT_ID_DEFAULTED",
|
|
10475
|
+
`Project "${identity.name}" declares no id in .wai/project.yaml, so it answers to "${identity.id}", derived from its display name \u2014 renaming the project would move it. No save writes it for you; declare it with \`id: ${identity.id}\` (stage 3 requires an explicit id).`
|
|
10476
|
+
);
|
|
10477
|
+
}
|
|
10478
|
+
const ambiguous = problem("ambiguous");
|
|
10479
|
+
if (ambiguous) {
|
|
10480
|
+
ctx.addIssue(
|
|
10481
|
+
"warning",
|
|
10482
|
+
"PROJECT_ID_AMBIGUOUS",
|
|
10483
|
+
`Project "${identity.name}" has no usable id: ${ambiguous.detail}. A project id is lower-case letters, digits, "-", "_" and ".", starting and ending with a letter or a digit \u2014 declare one in .wai/project.yaml.`
|
|
10484
|
+
);
|
|
10485
|
+
}
|
|
10486
|
+
if (problem("changed")) {
|
|
10487
|
+
const now = identity.id === void 0 ? "no id at all" : `"${identity.id}"`;
|
|
10488
|
+
ctx.addIssue(
|
|
10489
|
+
"error",
|
|
10490
|
+
"PROJECT_ID_CHANGED",
|
|
10491
|
+
`Project "${identity.name}" now resolves to ${now}, but its lock approved "${identity.lockedId}" \u2014 everything keyed on the approved id no longer finds this project. Restore \`id: ${identity.lockedId}\` in .wai/project.yaml, or rename deliberately and re-lock.`
|
|
10492
|
+
);
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10495
|
+
};
|
|
10496
|
+
}
|
|
10497
|
+
});
|
|
10498
|
+
|
|
10326
10499
|
// src/core/rules/integrity/roundtrip-serialization.ts
|
|
10327
10500
|
var roundtripRule;
|
|
10328
10501
|
var init_roundtrip_serialization = __esm({
|
|
@@ -10479,7 +10652,7 @@ var init_public_surface_binding = __esm({
|
|
|
10479
10652
|
check(ctx) {
|
|
10480
10653
|
for (const sub of ctx.subsystems) {
|
|
10481
10654
|
const isDraftCtx = isDraftSubsystem(sub);
|
|
10482
|
-
for (const pi of sub.publicInterfaces) {
|
|
10655
|
+
for (const pi of sub.publicInterfaces.filter(isOwnComponentEntry)) {
|
|
10483
10656
|
if (!pi.component) {
|
|
10484
10657
|
ctx.addIssue("error", "PUBLIC_INTERFACE_UNBOUND", `Subsystem "${sub.id}" declares a ${pi.type} public interface with no backing component. Bind it to the component that realizes it (publicInterfaces[].component).`, sub.id, isDraftCtx);
|
|
10485
10658
|
continue;
|
|
@@ -10580,13 +10753,15 @@ var init_public_surface_declared_type = __esm({
|
|
|
10580
10753
|
check(ctx) {
|
|
10581
10754
|
for (const sub of ctx.subsystems) {
|
|
10582
10755
|
const isDraftCtx = isDraftSubsystem(sub);
|
|
10583
|
-
for (const pi of sub.publicInterfaces) {
|
|
10756
|
+
for (const pi of sub.publicInterfaces.filter(isOwnComponentEntry)) {
|
|
10584
10757
|
const backing = pi.component ? ctx.componentMap.get(pi.component) : void 0;
|
|
10585
10758
|
if (!backing) continue;
|
|
10586
|
-
|
|
10587
|
-
|
|
10759
|
+
const piType = pi.type ?? "Custom";
|
|
10760
|
+
const piDetails = pi.details ?? "";
|
|
10761
|
+
if (!pubTypeMatches(piType, backing.componentType, backing.portalType)) {
|
|
10762
|
+
ctx.addIssue("error", "PUBLIC_INTERFACE_TYPE_MISMATCH", `Subsystem "${sub.id}" declares a ${pi.type} public interface backed by "${pi.component}" (${backing.componentType}${backing.portalType ? `/${backing.portalType}` : ""}), which cannot realize ${pi.type}. Expected ${expectedFor(piType)}.`, sub.id, isDraftCtx);
|
|
10588
10763
|
}
|
|
10589
|
-
if (
|
|
10764
|
+
if (piType === "Custom" && EVENT_VOCAB.test(piDetails)) {
|
|
10590
10765
|
const eventCapable = backing.componentType === "Observer" || backing.componentType === "Portal" && backing.portalType === "MessageBus";
|
|
10591
10766
|
if (!eventCapable) {
|
|
10592
10767
|
ctx.addIssue("warning", "PUBLIC_INTERFACE_EVENT_MISTYPED", `Subsystem "${sub.id}" declares a Custom public interface whose description implies an event/async boundary ("${pi.details}"), but it is backed by "${pi.component}" (${backing.componentType}${backing.portalType ? `/${backing.portalType}` : ""}), which cannot realize eventing. If this is genuinely event-driven, type it MessageBus and back it with an Observer or a Portal(MessageBus); otherwise reword the description to match the synchronous contract.`, sub.id, isDraftCtx);
|
|
@@ -10614,7 +10789,7 @@ var init_public_surface_bound_contract = __esm({
|
|
|
10614
10789
|
check(ctx) {
|
|
10615
10790
|
for (const sub of ctx.subsystems) {
|
|
10616
10791
|
const isDraftCtx = isDraftSubsystem(sub);
|
|
10617
|
-
for (const pi of sub.publicInterfaces) {
|
|
10792
|
+
for (const pi of sub.publicInterfaces.filter(isOwnComponentEntry)) {
|
|
10618
10793
|
if (!pi.interface) continue;
|
|
10619
10794
|
if (!pi.component || !ctx.componentMap.has(pi.component)) continue;
|
|
10620
10795
|
const intf = ctx.interfaceMap.get(pi.interface);
|
|
@@ -10630,6 +10805,54 @@ var init_public_surface_bound_contract = __esm({
|
|
|
10630
10805
|
}
|
|
10631
10806
|
});
|
|
10632
10807
|
|
|
10808
|
+
// src/core/rules/integrity/export-tables.ts
|
|
10809
|
+
var exportTablesRule;
|
|
10810
|
+
var init_export_tables = __esm({
|
|
10811
|
+
"src/core/rules/integrity/export-tables.ts"() {
|
|
10812
|
+
"use strict";
|
|
10813
|
+
init_models();
|
|
10814
|
+
exportTablesRule = {
|
|
10815
|
+
name: "export-tables",
|
|
10816
|
+
description: "Every level's export table must resolve the way a module resolver resolves `export \u2026 from`: one public name binds one target (EXPORT_ID_DUPLICATE when two wildcards \u2014 or two explicit entries \u2014 bind a name to different targets; the name is left out), no chain leads back to itself (EXPORT_CYCLE \u2014 an error for a named chain that never reaches a component or type, a warning for a wildcard cycle, which resolves to its union), every entry names a source and an item that exist, that the source exports and, for an own type, that the level owns, under a public name of [a-z0-9-_]+ (EXPORT_INVALID), and every re-exported component can serve a boundary caller \u2014 a Portal, a gateway being a Portal variant, or an Observer for events (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports). The rule judges the facts the export resolver gathered and resolves nothing itself.",
|
|
10817
|
+
codes: [
|
|
10818
|
+
{ code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
|
|
10819
|
+
{ code: "EXPORT_CYCLE", defaultSeverity: "error", summary: "A re-export chain leads back to itself (a warning for a wildcard cycle, which resolves to its union)" },
|
|
10820
|
+
{ code: "EXPORT_INVALID", defaultSeverity: "notice", summary: "An export entry names a missing source or item, an item its source does not export or its level does not own, or a malformed public name" },
|
|
10821
|
+
{ code: "EXPORT_UNCONSUMABLE", defaultSeverity: "notice", summary: "An exported component no boundary caller may reach: neither a Portal nor an Observer" }
|
|
10822
|
+
],
|
|
10823
|
+
check(ctx) {
|
|
10824
|
+
const tables = ctx.exportTables;
|
|
10825
|
+
if (!tables) return;
|
|
10826
|
+
for (const table of tables) {
|
|
10827
|
+
const sub = table.level === "subsystem" ? ctx.subsystems.find((s) => s.id === table.owner) : void 0;
|
|
10828
|
+
const draft = sub ? isDraftSubsystem(sub) : false;
|
|
10829
|
+
const where = table.level === "subsystem" ? `Subsystem "${table.owner}"` : `The project's L0 export table`;
|
|
10830
|
+
for (const problem of table.problems) {
|
|
10831
|
+
const name = problem.publicName ? `"${problem.publicName}"` : "an entry";
|
|
10832
|
+
switch (problem.kind) {
|
|
10833
|
+
case "duplicate":
|
|
10834
|
+
ctx.addIssue("error", "EXPORT_ID_DUPLICATE", `${where} binds ${name} to more than one target (${(problem.targets ?? []).join(", ")}), so the name is left out of the table. Narrow one of the wildcards, or add an explicit entry that picks the target.`, table.owner, draft);
|
|
10835
|
+
break;
|
|
10836
|
+
case "named-cycle":
|
|
10837
|
+
ctx.addIssue("error", "EXPORT_CYCLE", `${where} ${problem.detail}. Export the item from the level that owns it.`, table.owner, draft);
|
|
10838
|
+
break;
|
|
10839
|
+
case "wildcard-cycle":
|
|
10840
|
+
ctx.addIssue("warning", "EXPORT_CYCLE", `${where}: ${problem.detail}. Re-export in one direction only.`, table.owner, draft);
|
|
10841
|
+
break;
|
|
10842
|
+
case "invalid":
|
|
10843
|
+
ctx.addIssue("notice", "EXPORT_INVALID", `${where} ${problem.detail}.`, table.owner, draft);
|
|
10844
|
+
break;
|
|
10845
|
+
case "unconsumable":
|
|
10846
|
+
ctx.addIssue("notice", "EXPORT_UNCONSUMABLE", `${where}: ${problem.detail}.`, table.owner, draft);
|
|
10847
|
+
break;
|
|
10848
|
+
}
|
|
10849
|
+
}
|
|
10850
|
+
}
|
|
10851
|
+
}
|
|
10852
|
+
};
|
|
10853
|
+
}
|
|
10854
|
+
});
|
|
10855
|
+
|
|
10633
10856
|
// src/core/rules/integrity/lint-allows.ts
|
|
10634
10857
|
var lintAllowsRule;
|
|
10635
10858
|
var init_lint_allows = __esm({
|
|
@@ -12066,7 +12289,7 @@ var init_auth_source_wiring = __esm({
|
|
|
12066
12289
|
|
|
12067
12290
|
// src/core/rules/doctrine/subsystem-boundary-dependencies.ts
|
|
12068
12291
|
function restrictedConsumers(ctx, subsystemId, componentId) {
|
|
12069
|
-
const entries = (ctx.subsystems.find((s) => s.id === subsystemId)?.publicInterfaces ?? []).filter((pi) => pi.component === componentId);
|
|
12292
|
+
const entries = (ctx.subsystems.find((s) => s.id === subsystemId)?.publicInterfaces ?? []).filter((pi) => isOwnComponentEntry(pi) && pi.component === componentId);
|
|
12070
12293
|
if (entries.length === 0 || entries.some((pi) => pi.consumers === void 0)) return null;
|
|
12071
12294
|
return [...new Set(entries.flatMap((pi) => pi.consumers ?? []))];
|
|
12072
12295
|
}
|
|
@@ -12246,6 +12469,27 @@ var init_logic_dependency_class = __esm({
|
|
|
12246
12469
|
});
|
|
12247
12470
|
|
|
12248
12471
|
// src/core/rules/doctrine/data-block-dependencies.ts
|
|
12472
|
+
function indexReaches(ctx, from, target, seen = /* @__PURE__ */ new Set()) {
|
|
12473
|
+
for (const dep of from.dependsOn) {
|
|
12474
|
+
if (dep === target) return true;
|
|
12475
|
+
const next = ctx.componentMap.get(dep);
|
|
12476
|
+
if (!next || next.componentType !== "Index" || seen.has(dep)) continue;
|
|
12477
|
+
seen.add(dep);
|
|
12478
|
+
if (indexReaches(ctx, next, target, seen)) return true;
|
|
12479
|
+
}
|
|
12480
|
+
return false;
|
|
12481
|
+
}
|
|
12482
|
+
function derivedIndexBreach(ctx, from, to) {
|
|
12483
|
+
const ownership = ctx.ownershipIndex();
|
|
12484
|
+
const owner = ownership.ownerOf(from.id);
|
|
12485
|
+
if (!owner || owner !== ownership.ownerOf(to.id)) {
|
|
12486
|
+
return "An Index may project another Index only when one Repository owns both";
|
|
12487
|
+
}
|
|
12488
|
+
if (indexReaches(ctx, to, from.id)) {
|
|
12489
|
+
return `"${to.id}" depends back on "${from.id}" through Index edges \u2014 derived Indexes never form a cycle`;
|
|
12490
|
+
}
|
|
12491
|
+
return void 0;
|
|
12492
|
+
}
|
|
12249
12493
|
var dataBlockDepsRule;
|
|
12250
12494
|
var init_data_block_dependencies = __esm({
|
|
12251
12495
|
"src/core/rules/doctrine/data-block-dependencies.ts"() {
|
|
@@ -12253,12 +12497,12 @@ var init_data_block_dependencies = __esm({
|
|
|
12253
12497
|
init_stereotype_terms();
|
|
12254
12498
|
dataBlockDepsRule = {
|
|
12255
12499
|
name: "data-block-dependencies",
|
|
12256
|
-
description: "Keeps the data layer facing down. A Store may depend only on another Store, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse. A Registry is the write path to its Store and may depend on that Store, a backend Adapter, or the pure logic that validates the write. An Index is a read projection and a Query a computed read, each over its Store through a backend Adapter where one serves it. An Adapter is a sink toward the system: it may use pure logic, but never a Store or any other Orchestrator.",
|
|
12500
|
+
description: "Keeps the data layer facing down. A Store may depend only on another Store, a backend Adapter or pure logic \u2014 Registries and Indexes depend on the Store, never the reverse. A Registry is the write path to its Store and may depend on that Store, a backend Adapter, or the pure logic that validates the write. An Index is a read projection and a Query a computed read, each over its Store through a backend Adapter where one serves it; as an exceptional case an Index may project another Index of the same Repository, never in a cycle. An Adapter is a sink toward the system: it may use pure logic, but never a Store or any other Orchestrator.",
|
|
12257
12501
|
codes: [
|
|
12258
12502
|
{ code: "ARCHITECTURE_VIOLATION_STORE_DEP", defaultSeverity: "error", summary: "Store depending on anything but another Store, a backend Adapter or pure logic" },
|
|
12259
12503
|
{ code: "ARCHITECTURE_VIOLATION_REGISTRY_DEP", defaultSeverity: "warning", summary: "Registry depending on anything but its Store, a backend Adapter or pure logic (warning while new; aligned to the standard \xA77 validate\u2192write path)" },
|
|
12260
12504
|
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on a Store or on an Orchestrator that is not pure" },
|
|
12261
|
-
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter or
|
|
12505
|
+
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store, an Adapter, pure logic or another Index of its own Repository (never in a cycle)" },
|
|
12262
12506
|
{ code: "ARCHITECTURE_VIOLATION_QUERY_DEP", defaultSeverity: "error", summary: "Query depending on anything but its Store, a backend Adapter or pure logic" }
|
|
12263
12507
|
],
|
|
12264
12508
|
check(ctx) {
|
|
@@ -12295,11 +12539,13 @@ var init_data_block_dependencies = __esm({
|
|
|
12295
12539
|
edge.draftContext
|
|
12296
12540
|
);
|
|
12297
12541
|
}
|
|
12298
|
-
|
|
12542
|
+
const indexBreach = comp.componentType === "Index" && depComp.componentType === "Index" ? derivedIndexBreach(ctx, comp, depComp) : void 0;
|
|
12543
|
+
const derivedIndexOk = comp.componentType === "Index" && depComp.componentType === "Index" && !indexBreach;
|
|
12544
|
+
if (comp.componentType === "Index" && ownStoreOnly && !derivedIndexOk) {
|
|
12299
12545
|
ctx.addIssue(
|
|
12300
12546
|
"error",
|
|
12301
12547
|
"ARCHITECTURE_VIOLATION_INDEX_DEP",
|
|
12302
|
-
`Architectural violation: Index component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". An Index is a read projection and may depend only on its Store, a backend Adapter or pure logic
|
|
12548
|
+
`Architectural violation: Index component "${comp.id}" cannot depend on "${depComp.componentType}" component "${depComp.id}". An Index is a read projection and may depend only on its Store, a backend Adapter or pure logic \u2014 or, as an exceptional case, on another Index of the same Repository, never in a cycle.${indexBreach ? ` ${indexBreach}.` : ""}`,
|
|
12303
12549
|
comp.id,
|
|
12304
12550
|
edge.draftContext
|
|
12305
12551
|
);
|
|
@@ -17131,6 +17377,7 @@ var init_repository = __esm({
|
|
|
17131
17377
|
init_hierarchy_integrity();
|
|
17132
17378
|
init_reserved_id_segments();
|
|
17133
17379
|
init_namespace_shadowing();
|
|
17380
|
+
init_project_identity();
|
|
17134
17381
|
init_roundtrip_serialization();
|
|
17135
17382
|
init_type_declarations();
|
|
17136
17383
|
init_field_type_references();
|
|
@@ -17139,6 +17386,7 @@ var init_repository = __esm({
|
|
|
17139
17386
|
init_public_surface_consumers();
|
|
17140
17387
|
init_public_surface_declared_type();
|
|
17141
17388
|
init_public_surface_bound_contract();
|
|
17389
|
+
init_export_tables();
|
|
17142
17390
|
init_lint_allows();
|
|
17143
17391
|
init_contract_symmetry();
|
|
17144
17392
|
init_narrative_target_references();
|
|
@@ -17232,6 +17480,9 @@ var init_repository = __esm({
|
|
|
17232
17480
|
// that would anchor a bare reference to the root instead.
|
|
17233
17481
|
reservedIdSegmentsRule,
|
|
17234
17482
|
namespaceShadowingRule,
|
|
17483
|
+
// The project's own id, beside the spec ids: declared, well-formed, and the
|
|
17484
|
+
// one its lock approved.
|
|
17485
|
+
projectIdentityRule,
|
|
17235
17486
|
roundtripRule,
|
|
17236
17487
|
// The type vocabulary in three questions: what a type declares about
|
|
17237
17488
|
// itself, then the identifiers its fields name, then the ones its
|
|
@@ -17323,6 +17574,8 @@ var init_repository = __esm({
|
|
|
17323
17574
|
publicSurfaceConsumersRule,
|
|
17324
17575
|
publicSurfaceDeclaredTypeRule,
|
|
17325
17576
|
publicSurfaceBoundContractRule,
|
|
17577
|
+
// The export tables the resolver settled: what the published names bind.
|
|
17578
|
+
exportTablesRule,
|
|
17326
17579
|
cyclesRule,
|
|
17327
17580
|
// Semantic-edge family: dispatch/lifecycle validity BEFORE reachability so a
|
|
17328
17581
|
// reader sees the broken edge finding next to the unused-detection fallout
|
|
@@ -18467,7 +18720,7 @@ function buildRuleContext(opts) {
|
|
|
18467
18720
|
for (const sub of subsystems) {
|
|
18468
18721
|
publicSet.set(
|
|
18469
18722
|
sub.id,
|
|
18470
|
-
new Set(sub.publicInterfaces.map((pi) => pi.component).filter((c) => !!c))
|
|
18723
|
+
new Set(sub.publicInterfaces.filter(isOwnComponentEntry).map((pi) => pi.component).filter((c) => !!c))
|
|
18471
18724
|
);
|
|
18472
18725
|
}
|
|
18473
18726
|
const surfaceSnapshots = opts.surfaceSnapshots ?? [];
|
|
@@ -18571,7 +18824,7 @@ function buildRuleContext(opts) {
|
|
|
18571
18824
|
const candidates = [];
|
|
18572
18825
|
for (const snapshot of pool) {
|
|
18573
18826
|
if (provider !== void 0 && !isProvidedBy(snapshot, provider)) continue;
|
|
18574
|
-
const entry = snapshot.interfaces.find((e) => e.component === local || e.id ===
|
|
18827
|
+
const entry = snapshot.interfaces.find((e) => e.id === local) ?? snapshot.interfaces.find((e) => e.component === local && (e.componentType === void 0 || e.id === e.component || e.id === e.interface));
|
|
18575
18828
|
if (entry) candidates.push({ snapshot, entry });
|
|
18576
18829
|
}
|
|
18577
18830
|
if (candidates.length === 0) continue;
|
|
@@ -18810,6 +19063,8 @@ function buildRuleContext(opts) {
|
|
|
18810
19063
|
ext: { profiles: extensions.profiles, languages: extensions.languages, patterns: extensions.patterns, guarantees: extensions.guarantees, assertions: extensions.assertions, packSelections: opts.packSelections ?? [], selectionFailures: extensions.selectionFailures ?? [] },
|
|
18811
19064
|
variants: opts.variants ?? [],
|
|
18812
19065
|
surfaceSnapshots,
|
|
19066
|
+
...opts.projectIdentity ? { projectIdentity: opts.projectIdentity } : {},
|
|
19067
|
+
...opts.exportTables ? { exportTables: opts.exportTables } : {},
|
|
18813
19068
|
mountSurfaceSnapshots,
|
|
18814
19069
|
codeModel: opts.codeModel ?? emptyCodeModel(),
|
|
18815
19070
|
roundTripIssues: opts.roundTripIssues,
|
|
@@ -19046,6 +19301,17 @@ function projectPackSelections() {
|
|
|
19046
19301
|
return [];
|
|
19047
19302
|
}
|
|
19048
19303
|
}
|
|
19304
|
+
function boundProjectIdentity() {
|
|
19305
|
+
let config;
|
|
19306
|
+
try {
|
|
19307
|
+
config = loadProjectConfig();
|
|
19308
|
+
} catch {
|
|
19309
|
+
return void 0;
|
|
19310
|
+
}
|
|
19311
|
+
if (!config) return void 0;
|
|
19312
|
+
const lockedProjectId = readLockRecord()?.projectId;
|
|
19313
|
+
return projectIdentity(config, lockedProjectId);
|
|
19314
|
+
}
|
|
19049
19315
|
function findTestsReferencing2(methods, projectRoot2, testRoots = []) {
|
|
19050
19316
|
return findTestsReferencing(methods, projectRoot2, testRoots);
|
|
19051
19317
|
}
|
|
@@ -19262,8 +19528,17 @@ function validateSddTree(rulesOrOptions, projectType = "backend") {
|
|
|
19262
19528
|
scopeSubsystem,
|
|
19263
19529
|
extensions,
|
|
19264
19530
|
variants: loadProjectVariants(),
|
|
19531
|
+
// Every subsystem's resolved export table, then the project's: the
|
|
19532
|
+
// export-tables rule judges the problems the resolver met.
|
|
19533
|
+
exportTables: [
|
|
19534
|
+
...subsystems.map((s) => resolveSubsystemExports(s.id)),
|
|
19535
|
+
resolveProjectExports()
|
|
19536
|
+
],
|
|
19265
19537
|
// By-name selections only: a legacy path ref pins nothing to check.
|
|
19266
19538
|
packSelections: projectPackSelections(),
|
|
19539
|
+
// The run that IS the parent's verdict on a chained child judges the
|
|
19540
|
+
// child's specs, not the parent's identity, so it carries none.
|
|
19541
|
+
projectIdentity: crossTree === "off" ? void 0 : boundProjectIdentity(),
|
|
19267
19542
|
surfaceSnapshots,
|
|
19268
19543
|
mountSurfaceSnapshots,
|
|
19269
19544
|
codeModel,
|
|
@@ -19429,6 +19704,7 @@ var init_validation = __esm({
|
|
|
19429
19704
|
init_rules();
|
|
19430
19705
|
init_repository();
|
|
19431
19706
|
init_candidate();
|
|
19707
|
+
init_project();
|
|
19432
19708
|
init_gate_identity();
|
|
19433
19709
|
init_types();
|
|
19434
19710
|
init_source_analysis();
|
|
@@ -19564,7 +19840,7 @@ function loadSpecGraph() {
|
|
|
19564
19840
|
const implementations = loadImplementationSpecs();
|
|
19565
19841
|
const publicComponents = /* @__PURE__ */ new Set();
|
|
19566
19842
|
for (const sub of subsystems) {
|
|
19567
|
-
for (const pi of sub.publicInterfaces) {
|
|
19843
|
+
for (const pi of sub.publicInterfaces.filter(isOwnComponentEntry)) {
|
|
19568
19844
|
if (pi.component) publicComponents.add(pi.component);
|
|
19569
19845
|
}
|
|
19570
19846
|
}
|
|
@@ -19985,6 +20261,358 @@ var init_diagram = __esm({
|
|
|
19985
20261
|
}
|
|
19986
20262
|
});
|
|
19987
20263
|
|
|
20264
|
+
// src/core/exports.ts
|
|
20265
|
+
function localName2(id) {
|
|
20266
|
+
return id.split("::").pop() ?? id;
|
|
20267
|
+
}
|
|
20268
|
+
function ownedBy(subsystemId, declaringSubsystem) {
|
|
20269
|
+
return declaringSubsystem !== void 0 && (declaringSubsystem === subsystemId || declaringSubsystem.startsWith(`${subsystemId}::`));
|
|
20270
|
+
}
|
|
20271
|
+
function worldOf(subsystems, components, interfaces, types) {
|
|
20272
|
+
return {
|
|
20273
|
+
subsystems: new Map(subsystems.map((s) => [s.id, s])),
|
|
20274
|
+
components: new Map(components.map((c) => [c.id, c])),
|
|
20275
|
+
interfaces: new Map(interfaces.map((i) => [i.id, i])),
|
|
20276
|
+
types: types.reduce((m, t) => m.set(t.id, [...m.get(t.id) ?? [], t]), /* @__PURE__ */ new Map())
|
|
20277
|
+
};
|
|
20278
|
+
}
|
|
20279
|
+
function lookup(map, ref, namespace) {
|
|
20280
|
+
if (!ref) return void 0;
|
|
20281
|
+
return map.get(ref) ?? (namespace ? map.get(`${namespace}::${ref}`) : void 0);
|
|
20282
|
+
}
|
|
20283
|
+
function lookupType(world, ref, subsystemId) {
|
|
20284
|
+
const candidates = lookup(world.types, ref, subsystemId);
|
|
20285
|
+
return candidates?.find((t) => ownedBy(subsystemId, t.subsystem)) ?? candidates?.[0];
|
|
20286
|
+
}
|
|
20287
|
+
function checkPublicName(name, owner, problems) {
|
|
20288
|
+
if (!PUBLIC_NAME_RE.test(name)) {
|
|
20289
|
+
problems.push({
|
|
20290
|
+
kind: "invalid",
|
|
20291
|
+
owner,
|
|
20292
|
+
publicName: name,
|
|
20293
|
+
detail: `public name "${name}" breaks [a-z0-9-_]+ \u2014 rename it with \`as\``
|
|
20294
|
+
});
|
|
20295
|
+
}
|
|
20296
|
+
}
|
|
20297
|
+
function checkConsumable(entry, owner, problems) {
|
|
20298
|
+
if (entry.kind === "component" && entry.componentType && !CONSUMABLE.has(entry.componentType)) {
|
|
20299
|
+
problems.push({
|
|
20300
|
+
kind: "unconsumable",
|
|
20301
|
+
owner,
|
|
20302
|
+
publicName: entry.publicName,
|
|
20303
|
+
targets: [entry.component],
|
|
20304
|
+
detail: `"${entry.publicName}" exports ${entry.componentType} "${entry.component}", which no caller across the boundary may reach \u2014 only a Portal (a gateway is a Portal variant) or an Observer can serve one`
|
|
20305
|
+
});
|
|
20306
|
+
}
|
|
20307
|
+
}
|
|
20308
|
+
function findInSource(world, source, table, request) {
|
|
20309
|
+
if (request.typeDef) {
|
|
20310
|
+
const type = lookupType(world, request.typeDef, source.id);
|
|
20311
|
+
if (!type) return {};
|
|
20312
|
+
const item2 = { publicName: localName2(type.id), kind: "type", source: type.subsystem ?? source.id, typeDef: type.id, via: [] };
|
|
20313
|
+
return { item: item2, found: table.entries.find((e) => e.kind === "type" && e.typeDef === type.id && e.source === item2.source) };
|
|
20314
|
+
}
|
|
20315
|
+
const intf = lookup(world.interfaces, request.interface, source.id);
|
|
20316
|
+
const comp = lookup(world.components, request.component ?? intf?.component, source.id);
|
|
20317
|
+
if (!comp) return {};
|
|
20318
|
+
const narrowed = intf?.id ?? request.interface;
|
|
20319
|
+
const item = {
|
|
20320
|
+
publicName: localName2(narrowed ?? comp.id),
|
|
20321
|
+
kind: "component",
|
|
20322
|
+
source: comp.subsystem,
|
|
20323
|
+
component: comp.id,
|
|
20324
|
+
...narrowed ? { interface: narrowed } : {},
|
|
20325
|
+
componentType: comp.componentType,
|
|
20326
|
+
via: []
|
|
20327
|
+
};
|
|
20328
|
+
const exported = table.entries.filter((e) => e.kind === "component" && e.component === comp.id);
|
|
20329
|
+
const found = narrowed ? exported.find((e) => e.interface === narrowed) ?? exported.find((e) => e.interface === void 0) : exported.find((e) => e.interface === void 0) ?? exported[0];
|
|
20330
|
+
return { item, found: found && narrowed ? { ...found, interface: narrowed } : found };
|
|
20331
|
+
}
|
|
20332
|
+
function bindNamed(world, owner, source, sourceTable, request, publicName, inCycle, problems) {
|
|
20333
|
+
const { found, item } = findInSource(world, source, sourceTable, request);
|
|
20334
|
+
const what = request.typeDef ? `type "${request.typeDef}"` : `component "${request.component}"${request.interface ? ` (interface "${request.interface}")` : ""}`;
|
|
20335
|
+
if (!item) {
|
|
20336
|
+
problems.push({ kind: "invalid", owner, detail: `re-exports ${what} from "${source.id}", which has no such item` });
|
|
20337
|
+
return void 0;
|
|
20338
|
+
}
|
|
20339
|
+
const name = publicName(item);
|
|
20340
|
+
if (found) return { ...found, publicName: name, via: [source.id, ...found.via] };
|
|
20341
|
+
if (inCycle) {
|
|
20342
|
+
problems.push({ kind: "named-cycle", owner, publicName: name, targets: [owner, source.id], detail: `re-exports ${what} from "${source.id}", but the chain leads back to "${owner}" without reaching the item` });
|
|
20343
|
+
return void 0;
|
|
20344
|
+
}
|
|
20345
|
+
const declaring = item.source;
|
|
20346
|
+
if (!ownedBy(source.id, declaring)) {
|
|
20347
|
+
problems.push({ kind: "invalid", owner, publicName: name, detail: `re-exports ${what} from "${source.id}", which neither exports nor owns it` });
|
|
20348
|
+
return void 0;
|
|
20349
|
+
}
|
|
20350
|
+
problems.push({ kind: "invalid", owner, publicName: name, detail: `re-exports ${what} from "${source.id}", which does not export it \u2014 publish it there first` });
|
|
20351
|
+
return { ...item, publicName: name, via: [source.id] };
|
|
20352
|
+
}
|
|
20353
|
+
function bindOwn(world, sub, pi, problems) {
|
|
20354
|
+
if (pi.typeDef) {
|
|
20355
|
+
const type = lookupType(world, pi.typeDef, sub.id);
|
|
20356
|
+
if (!type || !ownedBy(sub.id, type.subsystem)) {
|
|
20357
|
+
problems.push({ kind: "invalid", owner: sub.id, detail: `exports type "${pi.typeDef}" as its own, but ${type ? `it belongs to "${type.subsystem ?? "the system"}"` : "no such type exists"}` });
|
|
20358
|
+
return void 0;
|
|
20359
|
+
}
|
|
20360
|
+
const name2 = pi.as ?? localName2(type.id);
|
|
20361
|
+
checkPublicName(name2, sub.id, problems);
|
|
20362
|
+
return { publicName: name2, kind: "type", source: type.subsystem ?? sub.id, typeDef: type.id, via: [] };
|
|
20363
|
+
}
|
|
20364
|
+
const comp = lookup(world.components, pi.component, sub.id);
|
|
20365
|
+
if (!comp || !ownedBy(sub.id, comp.subsystem)) return void 0;
|
|
20366
|
+
const intf = lookup(world.interfaces, pi.interface, sub.id);
|
|
20367
|
+
const narrowed = intf?.id ?? pi.interface;
|
|
20368
|
+
const name = pi.as ?? localName2(narrowed ?? comp.id);
|
|
20369
|
+
checkPublicName(name, sub.id, problems);
|
|
20370
|
+
return {
|
|
20371
|
+
publicName: name,
|
|
20372
|
+
kind: "component",
|
|
20373
|
+
source: sub.id,
|
|
20374
|
+
component: comp.id,
|
|
20375
|
+
...narrowed ? { interface: narrowed } : {},
|
|
20376
|
+
componentType: comp.componentType,
|
|
20377
|
+
...pi.type ? { type: pi.type } : {},
|
|
20378
|
+
...pi.details !== void 0 ? { details: pi.details } : {},
|
|
20379
|
+
via: []
|
|
20380
|
+
};
|
|
20381
|
+
}
|
|
20382
|
+
function subsystemCandidates(world, sub, tables, group, problems) {
|
|
20383
|
+
const out = [];
|
|
20384
|
+
sub.publicInterfaces.forEach((pi, index) => {
|
|
20385
|
+
if (pi.from === void 0) {
|
|
20386
|
+
const own = bindOwn(world, sub, pi, problems);
|
|
20387
|
+
if (own) out.push({ entry: own, explicit: true, from: index });
|
|
20388
|
+
return;
|
|
20389
|
+
}
|
|
20390
|
+
const source = world.subsystems.get(pi.from);
|
|
20391
|
+
if (!source || source.id === sub.id) {
|
|
20392
|
+
problems.push({ kind: "invalid", owner: sub.id, detail: source ? `re-exports from itself` : `re-exports from "${pi.from}", which is not a subsystem of this project` });
|
|
20393
|
+
return;
|
|
20394
|
+
}
|
|
20395
|
+
const sourceTable = tables.get(source.id) ?? emptyTable(source.id, "subsystem");
|
|
20396
|
+
if (pi.component !== void 0 || pi.typeDef !== void 0 || pi.interface !== void 0) {
|
|
20397
|
+
const bound2 = bindNamed(
|
|
20398
|
+
world,
|
|
20399
|
+
sub.id,
|
|
20400
|
+
source,
|
|
20401
|
+
sourceTable,
|
|
20402
|
+
{ component: pi.component, interface: pi.interface, typeDef: pi.typeDef },
|
|
20403
|
+
(item) => pi.as ?? localName2(pi.interface ?? item.typeDef ?? item.component ?? ""),
|
|
20404
|
+
group.has(source.id),
|
|
20405
|
+
problems
|
|
20406
|
+
);
|
|
20407
|
+
if (!bound2) return;
|
|
20408
|
+
checkPublicName(bound2.publicName, sub.id, problems);
|
|
20409
|
+
const entry = { ...bound2, ...pi.type ? { type: pi.type } : {}, ...pi.details !== void 0 ? { details: pi.details } : {} };
|
|
20410
|
+
checkConsumable(entry, sub.id, problems);
|
|
20411
|
+
out.push({ entry, explicit: true, from: index });
|
|
20412
|
+
return;
|
|
20413
|
+
}
|
|
20414
|
+
for (const e of sourceTable.entries) {
|
|
20415
|
+
const entry = { ...e, via: [source.id, ...e.via] };
|
|
20416
|
+
checkConsumable(entry, sub.id, problems);
|
|
20417
|
+
out.push({ entry, explicit: false, from: index });
|
|
20418
|
+
}
|
|
20419
|
+
});
|
|
20420
|
+
return out;
|
|
20421
|
+
}
|
|
20422
|
+
function emptyTable(owner, level) {
|
|
20423
|
+
return { owner, level, entries: [], problems: [] };
|
|
20424
|
+
}
|
|
20425
|
+
function settle(owner, level, candidates, problems) {
|
|
20426
|
+
const byName = /* @__PURE__ */ new Map();
|
|
20427
|
+
for (const c of candidates) {
|
|
20428
|
+
const list2 = byName.get(c.entry.publicName) ?? [];
|
|
20429
|
+
list2.push(c);
|
|
20430
|
+
byName.set(c.entry.publicName, list2);
|
|
20431
|
+
}
|
|
20432
|
+
const entries = [];
|
|
20433
|
+
for (const [name, list2] of byName) {
|
|
20434
|
+
const explicit = list2.filter((c) => c.explicit);
|
|
20435
|
+
const pool = explicit.length ? explicit : list2;
|
|
20436
|
+
const targets = [...new Set(pool.map((c) => exportTargetKey(c.entry)))];
|
|
20437
|
+
if (targets.length === 1) {
|
|
20438
|
+
entries.push(pool[0].entry);
|
|
20439
|
+
continue;
|
|
20440
|
+
}
|
|
20441
|
+
problems.push({
|
|
20442
|
+
kind: "duplicate",
|
|
20443
|
+
owner,
|
|
20444
|
+
publicName: name,
|
|
20445
|
+
targets,
|
|
20446
|
+
detail: `"${name}" is bound to ${targets.length} different targets (${targets.join(", ")}) by ${explicit.length ? "explicit entries" : "wildcard re-exports"}; it is left out of the table`
|
|
20447
|
+
});
|
|
20448
|
+
}
|
|
20449
|
+
return { owner, level, entries, problems };
|
|
20450
|
+
}
|
|
20451
|
+
function groupsInDependencyOrder(world) {
|
|
20452
|
+
const edges = /* @__PURE__ */ new Map();
|
|
20453
|
+
for (const sub of world.subsystems.values()) {
|
|
20454
|
+
edges.set(sub.id, sub.publicInterfaces.map((pi) => pi.from).filter((f) => f !== void 0 && f !== sub.id && world.subsystems.has(f)));
|
|
20455
|
+
}
|
|
20456
|
+
const index = /* @__PURE__ */ new Map();
|
|
20457
|
+
const low = /* @__PURE__ */ new Map();
|
|
20458
|
+
const stack = [];
|
|
20459
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
20460
|
+
const groups = [];
|
|
20461
|
+
let next = 0;
|
|
20462
|
+
const visit = (v) => {
|
|
20463
|
+
index.set(v, next);
|
|
20464
|
+
low.set(v, next);
|
|
20465
|
+
next++;
|
|
20466
|
+
stack.push(v);
|
|
20467
|
+
onStack.add(v);
|
|
20468
|
+
for (const w of edges.get(v) ?? []) {
|
|
20469
|
+
if (!index.has(w)) {
|
|
20470
|
+
visit(w);
|
|
20471
|
+
low.set(v, Math.min(low.get(v), low.get(w)));
|
|
20472
|
+
} else if (onStack.has(w)) low.set(v, Math.min(low.get(v), index.get(w)));
|
|
20473
|
+
}
|
|
20474
|
+
if (low.get(v) === index.get(v)) {
|
|
20475
|
+
const group = [];
|
|
20476
|
+
let w;
|
|
20477
|
+
do {
|
|
20478
|
+
w = stack.pop();
|
|
20479
|
+
onStack.delete(w);
|
|
20480
|
+
group.push(w);
|
|
20481
|
+
} while (w !== v);
|
|
20482
|
+
groups.push(group.sort());
|
|
20483
|
+
}
|
|
20484
|
+
};
|
|
20485
|
+
for (const id of [...world.subsystems.keys()].sort()) if (!index.has(id)) visit(id);
|
|
20486
|
+
return groups;
|
|
20487
|
+
}
|
|
20488
|
+
function fingerprint(table) {
|
|
20489
|
+
return table.entries.map((e) => `${e.publicName}=${exportTargetKey(e)}`).sort().join("|");
|
|
20490
|
+
}
|
|
20491
|
+
function resolveSubsystemTables(subsystems, components, interfaces, types) {
|
|
20492
|
+
const world = worldOf(subsystems, components, interfaces, types);
|
|
20493
|
+
const tables = /* @__PURE__ */ new Map();
|
|
20494
|
+
for (const group of groupsInDependencyOrder(world)) {
|
|
20495
|
+
const members = new Set(group);
|
|
20496
|
+
const cyclic = group.length > 1;
|
|
20497
|
+
for (let pass = 0, changed = true; changed && pass <= (cyclic ? group.length * 4 + 4 : 0); pass++) {
|
|
20498
|
+
changed = false;
|
|
20499
|
+
for (const id of group) {
|
|
20500
|
+
const problems = [];
|
|
20501
|
+
const table = settle(id, "subsystem", subsystemCandidates(world, world.subsystems.get(id), tables, cyclic ? members : /* @__PURE__ */ new Set(), problems), problems);
|
|
20502
|
+
const before = tables.get(id);
|
|
20503
|
+
if (!before || fingerprint(before) !== fingerprint(table)) changed = true;
|
|
20504
|
+
tables.set(id, table);
|
|
20505
|
+
}
|
|
20506
|
+
}
|
|
20507
|
+
const wildcardLoop = cyclic && group.some((id) => world.subsystems.get(id).publicInterfaces.some((pi) => pi.from !== void 0 && pi.component === void 0 && pi.typeDef === void 0 && pi.interface === void 0 && members.has(pi.from)));
|
|
20508
|
+
if (wildcardLoop) {
|
|
20509
|
+
tables.get(group[0]).problems.push({
|
|
20510
|
+
kind: "wildcard-cycle",
|
|
20511
|
+
owner: group[0],
|
|
20512
|
+
targets: group,
|
|
20513
|
+
detail: `subsystems ${group.map((g) => `"${g}"`).join(", ")} re-export each other through wildcards; their tables resolve to the union, but they form one circular module surface`
|
|
20514
|
+
});
|
|
20515
|
+
}
|
|
20516
|
+
}
|
|
20517
|
+
return tables;
|
|
20518
|
+
}
|
|
20519
|
+
function sourceOf(world, e) {
|
|
20520
|
+
if (e.from ?? e.subsystem) return e.from ?? e.subsystem;
|
|
20521
|
+
if (e.component) return world.components.get(e.component)?.subsystem;
|
|
20522
|
+
if (e.interface) {
|
|
20523
|
+
const intf = world.interfaces.get(e.interface);
|
|
20524
|
+
return intf ? world.components.get(intf.component)?.subsystem : void 0;
|
|
20525
|
+
}
|
|
20526
|
+
if (e.typeDef) return world.types.get(e.typeDef)?.[0]?.subsystem;
|
|
20527
|
+
return void 0;
|
|
20528
|
+
}
|
|
20529
|
+
function withEntryMetadata(entry, e) {
|
|
20530
|
+
return {
|
|
20531
|
+
...entry,
|
|
20532
|
+
audience: e.audience ?? "instance",
|
|
20533
|
+
...e.name ? { name: e.name } : {},
|
|
20534
|
+
...e.type ? { type: e.type } : {},
|
|
20535
|
+
...e.details !== void 0 ? { details: e.details } : {},
|
|
20536
|
+
...e.authPolicy ? { authPolicy: e.authPolicy } : {},
|
|
20537
|
+
...e.version ? { version: e.version } : {},
|
|
20538
|
+
...e.stability ? { stability: e.stability } : {}
|
|
20539
|
+
};
|
|
20540
|
+
}
|
|
20541
|
+
function resolveProjectTable(system, subsystems, components, interfaces, types, subsystemTables) {
|
|
20542
|
+
const world = worldOf(subsystems, components, interfaces, types);
|
|
20543
|
+
const owner = system.name;
|
|
20544
|
+
const problems = [];
|
|
20545
|
+
const candidates = [];
|
|
20546
|
+
(system.publicInterfaces ?? []).forEach((e, index) => {
|
|
20547
|
+
const sourceId = sourceOf(world, e);
|
|
20548
|
+
const source = sourceId ? world.subsystems.get(sourceId) : void 0;
|
|
20549
|
+
if (!source) {
|
|
20550
|
+
const label = e.as ?? e.id ?? e.interface ?? e.component ?? e.typeDef ?? `#${index + 1}`;
|
|
20551
|
+
problems.push({ kind: "invalid", owner, publicName: label, detail: sourceId ? `"${label}" re-exports from "${sourceId}", which is not a subsystem of this project` : `"${label}" names no source subsystem and no item whose owner could supply one` });
|
|
20552
|
+
return;
|
|
20553
|
+
}
|
|
20554
|
+
const sourceTable = subsystemTables.get(source.id) ?? emptyTable(source.id, "subsystem");
|
|
20555
|
+
if (e.component !== void 0 || e.typeDef !== void 0 || e.interface !== void 0) {
|
|
20556
|
+
const bound2 = bindNamed(
|
|
20557
|
+
world,
|
|
20558
|
+
owner,
|
|
20559
|
+
source,
|
|
20560
|
+
sourceTable,
|
|
20561
|
+
{ component: e.component, interface: e.interface, typeDef: e.typeDef },
|
|
20562
|
+
() => e.as ?? e.id ?? e.interface ?? e.component ?? e.typeDef,
|
|
20563
|
+
false,
|
|
20564
|
+
problems
|
|
20565
|
+
);
|
|
20566
|
+
if (!bound2) return;
|
|
20567
|
+
checkPublicName(bound2.publicName, owner, problems);
|
|
20568
|
+
const entry = withEntryMetadata(bound2, e);
|
|
20569
|
+
checkConsumable(entry, owner, problems);
|
|
20570
|
+
candidates.push({ entry, explicit: true, from: index });
|
|
20571
|
+
return;
|
|
20572
|
+
}
|
|
20573
|
+
for (const found of sourceTable.entries) {
|
|
20574
|
+
const entry = withEntryMetadata({ ...found, via: [source.id, ...found.via] }, e);
|
|
20575
|
+
checkConsumable(entry, owner, problems);
|
|
20576
|
+
candidates.push({ entry, explicit: false, from: index });
|
|
20577
|
+
}
|
|
20578
|
+
});
|
|
20579
|
+
return settle(owner, "project", candidates, problems);
|
|
20580
|
+
}
|
|
20581
|
+
function scanTables(subsystems) {
|
|
20582
|
+
const memo = subsystemMemo.get(subsystems);
|
|
20583
|
+
if (memo) return memo;
|
|
20584
|
+
const tables = resolveSubsystemTables(subsystems, loadComponentSpecs(), loadInterfaceSpecs(), loadTypeSpecs());
|
|
20585
|
+
subsystemMemo.set(subsystems, tables);
|
|
20586
|
+
return tables;
|
|
20587
|
+
}
|
|
20588
|
+
function resolveSubsystemExportTable(subsystemId) {
|
|
20589
|
+
const tables = scanTables(loadSubsystemSpecs());
|
|
20590
|
+
return tables.get(subsystemId) ?? emptyTable(subsystemId, "subsystem");
|
|
20591
|
+
}
|
|
20592
|
+
function resolveProjectExportTable() {
|
|
20593
|
+
const system = loadSystemSpec();
|
|
20594
|
+
if (!system) return emptyTable("", "project");
|
|
20595
|
+
const subsystems = loadSubsystemSpecs();
|
|
20596
|
+
const key = JSON.stringify([system.name, system.publicInterfaces ?? []]);
|
|
20597
|
+
const memo = projectMemo.get(subsystems);
|
|
20598
|
+
if (memo && memo.key === key) return memo.table;
|
|
20599
|
+
const subsystemTables = new Map(subsystems.map((sub) => [sub.id, resolveSubsystemExportTable(sub.id)]));
|
|
20600
|
+
const table = resolveProjectTable(system, subsystems, loadComponentSpecs(), loadInterfaceSpecs(), loadTypeSpecs(), subsystemTables);
|
|
20601
|
+
projectMemo.set(subsystems, { key, table });
|
|
20602
|
+
return table;
|
|
20603
|
+
}
|
|
20604
|
+
var CONSUMABLE, subsystemMemo, projectMemo;
|
|
20605
|
+
var init_exports2 = __esm({
|
|
20606
|
+
"src/core/exports.ts"() {
|
|
20607
|
+
"use strict";
|
|
20608
|
+
init_exports();
|
|
20609
|
+
init_specs2();
|
|
20610
|
+
CONSUMABLE = /* @__PURE__ */ new Set(["Portal", "Observer"]);
|
|
20611
|
+
subsystemMemo = /* @__PURE__ */ new WeakMap();
|
|
20612
|
+
projectMemo = /* @__PURE__ */ new WeakMap();
|
|
20613
|
+
}
|
|
20614
|
+
});
|
|
20615
|
+
|
|
19988
20616
|
// src/core/specs.ts
|
|
19989
20617
|
function emptyIndex() {
|
|
19990
20618
|
return {
|
|
@@ -20232,13 +20860,18 @@ function mergeMountRealizations(subs) {
|
|
|
20232
20860
|
function stripNamespaceFromSubsystem(spec, prefix) {
|
|
20233
20861
|
const memberPrefix = spec.projectPath ? spec.id : prefix;
|
|
20234
20862
|
const relMember = (id) => id.includes("::") ? relativizeId(id, memberPrefix) : id;
|
|
20863
|
+
const relPeer = (id) => id.includes("::") ? relativizeId(id, prefix) : id;
|
|
20235
20864
|
return {
|
|
20236
20865
|
...spec,
|
|
20237
20866
|
id: relativizeId(spec.id, prefix),
|
|
20238
20867
|
publicInterfaces: spec.publicInterfaces.map((pi) => ({
|
|
20239
20868
|
...pi,
|
|
20240
|
-
|
|
20241
|
-
|
|
20869
|
+
// A re-export names a peer's item, written relative to the namespace the
|
|
20870
|
+
// subsystem is declared in (the loader's inverse), not its members'.
|
|
20871
|
+
component: pi.component ? pi.from ? relPeer(pi.component) : relMember(pi.component) : void 0,
|
|
20872
|
+
interface: pi.interface ? pi.from ? relPeer(pi.interface) : relMember(pi.interface) : void 0,
|
|
20873
|
+
...pi.typeDef ? { typeDef: pi.from ? relPeer(pi.typeDef) : relMember(pi.typeDef) } : {},
|
|
20874
|
+
...pi.from ? { from: relPeer(pi.from) } : {},
|
|
20242
20875
|
// consumers name SUBSYSTEMS — peers of this one, so they are relative to
|
|
20243
20876
|
// the namespace the subsystem itself is declared in, never its members'.
|
|
20244
20877
|
...pi.consumers ? { consumers: pi.consumers.map((c) => c.includes("::") ? relativizeId(c, prefix) : c) } : {}
|
|
@@ -20513,6 +21146,14 @@ function identityKeyOf(field, item) {
|
|
|
20513
21146
|
}
|
|
20514
21147
|
function publicInterfaceKey(o) {
|
|
20515
21148
|
const str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
21149
|
+
const from = str(o.from);
|
|
21150
|
+
const typeDef = str(o.typeDef);
|
|
21151
|
+
if (from || typeDef) {
|
|
21152
|
+
const item = typeDef ? `type ${typeDef}` : str(o.component) ?? "*";
|
|
21153
|
+
const narrowed = str(o.interface) ? `.${String(o.interface)}` : "";
|
|
21154
|
+
const renamed = str(o.as) ? ` as ${String(o.as)}` : "";
|
|
21155
|
+
return `${from ? `${from}:` : ""}${item}${narrowed}${renamed}`;
|
|
21156
|
+
}
|
|
20516
21157
|
const component = str(o.component);
|
|
20517
21158
|
if (component) return str(o.interface) ? `${component}.${String(o.interface)}` : component;
|
|
20518
21159
|
if (typeof o.type === "string" && typeof o.details === "string") return `${o.type} ${o.details}`;
|
|
@@ -20717,8 +21358,9 @@ function identityFieldsOf(field) {
|
|
|
20717
21358
|
case "dispatch":
|
|
20718
21359
|
return ["capability"];
|
|
20719
21360
|
// Bound: component + interface; not yet bound: type + details.
|
|
21361
|
+
// Re-exports and type exports: from + item + interface + as.
|
|
20720
21362
|
case "publicInterfaces":
|
|
20721
|
-
return ["component", "interface", "type", "details"];
|
|
21363
|
+
return ["component", "interface", "type", "details", "from", "typeDef", "as"];
|
|
20722
21364
|
case "lifecycle":
|
|
20723
21365
|
return ["phase", "component", "method"];
|
|
20724
21366
|
case "emits":
|
|
@@ -21057,6 +21699,12 @@ function loadTypeSpecs() {
|
|
|
21057
21699
|
function loadTypeSpec(id) {
|
|
21058
21700
|
return current().loadTypeSpec(id);
|
|
21059
21701
|
}
|
|
21702
|
+
function resolveSubsystemExports(subsystemId) {
|
|
21703
|
+
return resolveSubsystemExportTable(subsystemId);
|
|
21704
|
+
}
|
|
21705
|
+
function resolveProjectExports() {
|
|
21706
|
+
return resolveProjectExportTable();
|
|
21707
|
+
}
|
|
21060
21708
|
function saveTypeSpec(spec) {
|
|
21061
21709
|
return current().saveTypeSpec(spec);
|
|
21062
21710
|
}
|
|
@@ -21170,6 +21818,7 @@ var init_specs2 = __esm({
|
|
|
21170
21818
|
init_models();
|
|
21171
21819
|
init_narrative_labels();
|
|
21172
21820
|
init_diagram();
|
|
21821
|
+
init_exports2();
|
|
21173
21822
|
NO_ROOT_SUBSYSTEMS = /* @__PURE__ */ new Set();
|
|
21174
21823
|
SIGNATURE_TTL_MS = 2e3;
|
|
21175
21824
|
COMPONENT_AUTH_SOURCE = "component:";
|
|
@@ -21348,14 +21997,19 @@ var init_specs2 = __esm({
|
|
|
21348
21997
|
return {
|
|
21349
21998
|
...sub,
|
|
21350
21999
|
id: qualifiedSubId,
|
|
21351
|
-
publicInterfaces: sub.publicInterfaces.map((p) =>
|
|
21352
|
-
|
|
21353
|
-
|
|
21354
|
-
|
|
21355
|
-
|
|
21356
|
-
|
|
21357
|
-
|
|
21358
|
-
|
|
22000
|
+
publicInterfaces: sub.publicInterfaces.map((p) => {
|
|
22001
|
+
const itemPrefix = p.from ? namespacePrefix : componentPrefix;
|
|
22002
|
+
return {
|
|
22003
|
+
...p,
|
|
22004
|
+
component: p.component ? qualifyId(p.component, itemPrefix, this.rootSubsystems) : void 0,
|
|
22005
|
+
interface: p.interface ? qualifyId(p.interface, itemPrefix, this.rootSubsystems) : void 0,
|
|
22006
|
+
...p.typeDef ? { typeDef: qualifyId(p.typeDef, itemPrefix, this.rootSubsystems) } : {},
|
|
22007
|
+
...p.from ? { from: qualifySubsystemRef(p.from, namespacePrefix, this.rootSubsystems) } : {},
|
|
22008
|
+
// Each consumer is a subsystem reference: qualified into the namespace
|
|
22009
|
+
// this subsystem is declared in, like a component's `subsystem` field.
|
|
22010
|
+
...p.consumers ? { consumers: p.consumers.map((c) => qualifySubsystemRef(c, namespacePrefix, this.rootSubsystems)) } : {}
|
|
22011
|
+
};
|
|
22012
|
+
}),
|
|
21359
22013
|
lifecycle: sub.lifecycle?.map((le) => ({
|
|
21360
22014
|
...le,
|
|
21361
22015
|
component: qualifyId(le.component, componentPrefix, this.rootSubsystems)
|
|
@@ -22967,8 +23621,12 @@ var init_specs2 = __esm({
|
|
|
22967
23621
|
if (Array.isArray(out.publicInterfaces)) {
|
|
22968
23622
|
out.publicInterfaces = out.publicInterfaces.map((pi) => ({
|
|
22969
23623
|
...pi,
|
|
22970
|
-
|
|
22971
|
-
|
|
23624
|
+
// A re-export names a peer's item: qualified into the declaring
|
|
23625
|
+
// namespace (q), not this subsystem's members' (qm).
|
|
23626
|
+
...typeof pi?.component === "string" ? { component: (typeof pi?.from === "string" ? q2 : qm)(pi.component) } : {},
|
|
23627
|
+
...typeof pi?.interface === "string" ? { interface: (typeof pi?.from === "string" ? q2 : qm)(pi.interface) } : {},
|
|
23628
|
+
...typeof pi?.typeDef === "string" ? { typeDef: (typeof pi?.from === "string" ? q2 : qm)(pi.typeDef) } : {},
|
|
23629
|
+
...typeof pi?.from === "string" ? { from: qualifySubsystemRef(pi.from, prefix, this.rootSubsystems) } : {},
|
|
22972
23630
|
...Array.isArray(pi?.consumers) ? { consumers: pi.consumers.map((c) => typeof c === "string" ? qualifySubsystemRef(c, prefix, this.rootSubsystems) : c) } : {}
|
|
22973
23631
|
}));
|
|
22974
23632
|
}
|
|
@@ -23662,9 +24320,11 @@ var init_domain_curator = __esm({
|
|
|
23662
24320
|
});
|
|
23663
24321
|
|
|
23664
24322
|
// src/core/provision.ts
|
|
23665
|
-
function defaultProjectConfig(name, now) {
|
|
24323
|
+
function defaultProjectConfig(name, now, id) {
|
|
24324
|
+
const declared = id ?? effectiveProjectId({ name });
|
|
23666
24325
|
return {
|
|
23667
24326
|
schemaVersion: "1.0.0",
|
|
24327
|
+
...declared !== null ? { id: declared } : {},
|
|
23668
24328
|
name,
|
|
23669
24329
|
projectType: "backend",
|
|
23670
24330
|
targets: [{ type: "claude", outputDir: ".claude/agents", enabled: true }],
|
|
@@ -23706,7 +24366,7 @@ function provisionProject(name) {
|
|
|
23706
24366
|
projectConfigRepository.create(config);
|
|
23707
24367
|
saveSystemSpec(bootstrapSystemSpec(name, now));
|
|
23708
24368
|
}
|
|
23709
|
-
function ensureProjectInitialized(fallbackName) {
|
|
24369
|
+
function ensureProjectInitialized(fallbackName, id) {
|
|
23710
24370
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23711
24371
|
const paths = aiPathsAt(getProjectRoot());
|
|
23712
24372
|
const hasSystem = fs14.existsSync(paths.specsSystem());
|
|
@@ -23718,7 +24378,7 @@ function ensureProjectInitialized(fallbackName) {
|
|
|
23718
24378
|
let wroteConfig = false;
|
|
23719
24379
|
let wroteSystem = false;
|
|
23720
24380
|
if (!projectConfigRepository.exists()) {
|
|
23721
|
-
projectConfigRepository.create(defaultProjectConfig(name, now));
|
|
24381
|
+
projectConfigRepository.create(defaultProjectConfig(name, now, id));
|
|
23722
24382
|
wroteConfig = true;
|
|
23723
24383
|
}
|
|
23724
24384
|
if (!hasSystem) {
|
|
@@ -23799,7 +24459,7 @@ function backfillChainedSubprojectConfigs(projectRoot2) {
|
|
|
23799
24459
|
walkChainedSubprojects(projectRoot2, (childDir, subsystemId) => {
|
|
23800
24460
|
if (childHasSpecsButNoConfig(childDir)) {
|
|
23801
24461
|
runWithProjectRoot(childDir, () => {
|
|
23802
|
-
ensureProjectInitialized(subsystemId);
|
|
24462
|
+
ensureProjectInitialized(subsystemId, localSegment(subsystemId));
|
|
23803
24463
|
});
|
|
23804
24464
|
backfilled.push(childDir);
|
|
23805
24465
|
}
|
|
@@ -23815,7 +24475,7 @@ function createChainedSubsystem(subsystem, projectName) {
|
|
|
23815
24475
|
const childDir = assertContainedProjectPath(getProjectRoot(), projectPath);
|
|
23816
24476
|
runWithProjectRoot(childDir, () => {
|
|
23817
24477
|
ensureDir(aiPathsAt(childDir).specsDir());
|
|
23818
|
-
ensureProjectInitialized(projectName);
|
|
24478
|
+
ensureProjectInitialized(projectName, localSegment(subsystem.id));
|
|
23819
24479
|
});
|
|
23820
24480
|
invalidateSpecCache();
|
|
23821
24481
|
}
|
|
@@ -23846,6 +24506,9 @@ function moveSubsystemProject(subsystemId, newProjectPath) {
|
|
|
23846
24506
|
saveSubsystemSpec({ ...sub, projectPath: nextPath, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
23847
24507
|
invalidateSpecCache();
|
|
23848
24508
|
}
|
|
24509
|
+
function localSegment(subsystemId) {
|
|
24510
|
+
return subsystemId.split("::").pop();
|
|
24511
|
+
}
|
|
23849
24512
|
function toPosixPath(p) {
|
|
23850
24513
|
return p.replace(/\\/g, "/");
|
|
23851
24514
|
}
|
|
@@ -23882,7 +24545,7 @@ function externalizeSubsystem(subsystemId, projectPath) {
|
|
|
23882
24545
|
const childSystemName = foo.name || subsystemId;
|
|
23883
24546
|
runWithProjectRoot(childDir, () => {
|
|
23884
24547
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23885
|
-
projectConfigRepository.create(defaultProjectConfig(childSystemName, now));
|
|
24548
|
+
projectConfigRepository.create(defaultProjectConfig(childSystemName, now, subsystemId));
|
|
23886
24549
|
ensureDir(path19.join(childDir, ".wai", "specs"));
|
|
23887
24550
|
saveSystemSpec(bootstrapSystemSpec(childSystemName, now));
|
|
23888
24551
|
});
|
|
@@ -24292,6 +24955,7 @@ var init_provision = __esm({
|
|
|
24292
24955
|
init_fs();
|
|
24293
24956
|
init_yaml();
|
|
24294
24957
|
init_errors();
|
|
24958
|
+
init_project();
|
|
24295
24959
|
init_identity_rename();
|
|
24296
24960
|
init_models();
|
|
24297
24961
|
METHOD_NAME = /^[a-z][a-zA-Z0-9]*$/;
|
|
@@ -30788,7 +31452,13 @@ function qualifiedComponentId(id) {
|
|
|
30788
31452
|
return chained.length === 1 ? chained[0] : id;
|
|
30789
31453
|
}
|
|
30790
31454
|
function publicInterfacesReplacement(subsystem, stored, list2) {
|
|
30791
|
-
const identity = (pi) =>
|
|
31455
|
+
const identity = (pi) => {
|
|
31456
|
+
if (pi.from || pi.typeDef) {
|
|
31457
|
+
const item = pi.typeDef ? `type ${pi.typeDef}` : pi.component ?? "*";
|
|
31458
|
+
return `${pi.from ? `${pi.from}:` : ""}${item}${pi.interface ? `.${pi.interface}` : ""}${pi.as ? ` as ${pi.as}` : ""}`;
|
|
31459
|
+
}
|
|
31460
|
+
return pi.component ? `${pi.component}${pi.interface ? `.${pi.interface}` : ""}` : `${pi.type} ${pi.details}`;
|
|
31461
|
+
};
|
|
30792
31462
|
const restated = /* @__PURE__ */ new Map();
|
|
30793
31463
|
for (const pi of list2) restated.set(identity(pi), (restated.get(identity(pi)) ?? 0) + 1);
|
|
30794
31464
|
const twice = [...restated].filter(([, n]) => n > 1).map(([key]) => `"${key}"`);
|
|
@@ -30801,7 +31471,7 @@ function publicInterfacesReplacement(subsystem, stored, list2) {
|
|
|
30801
31471
|
const key = identity(pi);
|
|
30802
31472
|
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
30803
31473
|
if (seen.get(key) > (restated.get(key) ?? 0)) {
|
|
30804
|
-
deletes.push(pi.component ? { component: pi.component, interface: pi.interface, action: "delete" } : { type: pi.type, details: pi.details, action: "delete" });
|
|
31474
|
+
deletes.push(pi.from || pi.typeDef ? { from: pi.from, component: pi.component, interface: pi.interface, typeDef: pi.typeDef, as: pi.as, action: "delete" } : pi.component ? { component: pi.component, interface: pi.interface, action: "delete" } : { type: pi.type, details: pi.details, action: "delete" });
|
|
30805
31475
|
}
|
|
30806
31476
|
}
|
|
30807
31477
|
const restatedEntries = list2.map((pi) => {
|
|
@@ -31266,17 +31936,21 @@ function createMcpServer(options = {}) {
|
|
|
31266
31936
|
}
|
|
31267
31937
|
}
|
|
31268
31938
|
);
|
|
31939
|
+
const publicInterfaceItemInput = import_zod11.z.object({
|
|
31940
|
+
type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]).optional().describe("Transport kind of an own item (required on an own component item); a re-export inherits its target's"),
|
|
31941
|
+
details: import_zod11.z.string().optional().describe("Consumer-facing description of an own item (required on an own component item); a re-export inherits its target's"),
|
|
31942
|
+
component: import_zod11.z.string().optional().describe("For an own item, the L2 component that realizes it (this subsystem's published surface); for a re-export, the component re-exported from `from`"),
|
|
31943
|
+
interface: import_zod11.z.string().optional().describe("Optional L3 interface id the entry narrows to"),
|
|
31944
|
+
consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER)."),
|
|
31945
|
+
from: import_zod11.z.string().optional().describe("Re-export source: another subsystem of this project. With neither component nor typeDef, the entry re-exports everything that subsystem exports (a wildcard)"),
|
|
31946
|
+
typeDef: import_zod11.z.string().optional().describe("A type exported as is: owned by this subsystem, or re-exported from `from`"),
|
|
31947
|
+
as: import_zod11.z.string().optional().describe("The public name; defaults to the narrowed interface, then the component or type id. Must fit [a-z0-9-_]+ (EXPORT_INVALID otherwise)")
|
|
31948
|
+
}).strict();
|
|
31269
31949
|
const subsystemInput = {
|
|
31270
31950
|
id: import_zod11.z.string().describe("Lowercase identifier for the subsystem"),
|
|
31271
31951
|
name: import_zod11.z.string().describe("Human-readable display name"),
|
|
31272
31952
|
description: import_zod11.z.string().describe("Purpose and details of the subsystem"),
|
|
31273
|
-
publicInterfaces: import_zod11.z.array(
|
|
31274
|
-
type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
|
|
31275
|
-
details: import_zod11.z.string(),
|
|
31276
|
-
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface (this subsystem's published surface)"),
|
|
31277
|
-
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component backing this entry"),
|
|
31278
|
-
consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
|
|
31279
|
-
}).strict()).optional().describe("Public entrypoints exposed by this subsystem, each bound to a realizing component"),
|
|
31953
|
+
publicInterfaces: import_zod11.z.array(publicInterfaceItemInput).optional().describe("The subsystem's export table: its own entrypoints, each bound to a realizing component, and re-exports of what other subsystems of this project export"),
|
|
31280
31954
|
projectPath: import_zod11.z.string().optional().describe("Relative path to external project root for subsystem chaining"),
|
|
31281
31955
|
targetLanguage: import_zod11.z.string().optional().describe("Override of the system-level targetLanguage for this subsystem"),
|
|
31282
31956
|
profile: import_zod11.z.string().optional().describe("Architectural profile override for this subsystem (built-ins: backend, frontend-reactive, frontend-controller, lowlevel-os, game-ecs, realtime-embedded, plc-cyclic; extension packs may add more \u2014 unknown names get UNKNOWN_PROFILE)"),
|
|
@@ -31338,16 +32012,10 @@ function createMcpServer(options = {}) {
|
|
|
31338
32012
|
server,
|
|
31339
32013
|
"sdd_set_public_interfaces",
|
|
31340
32014
|
{
|
|
31341
|
-
description: "Set (replace) an existing subsystem's publicInterfaces
|
|
32015
|
+
description: "Set (replace) an existing subsystem's export table (its publicInterfaces). An OWN item binds type + details to the component (and optional interface) that realizes it, or exports a type the subsystem owns (typeDef). A RE-EXPORT names another subsystem of the project in `from` and one component (optionally narrowed with `interface`) or one typeDef from what it exports, optionally renamed with `as` \u2014 `from` alone re-exports everything it exports; type and details are optional on a re-export, which inherits its target's. Use this to backfill bindings once the subsystem's components exist \u2014 cross-subsystem dependencies may only target a component its owning subsystem publishes; a re-export is a name, not a licence. A replacement, not a merge: an entry left out is removed. An own entry's identity is its component and interface, a re-export's its source, item and name, so a list naming one identity twice is refused. Written through the authoring seam as one gated delta, and answered with the change report \u2014 every entry added, changed or removed, or that nothing changed \u2014 as structured content beside the sentence.",
|
|
31342
32016
|
inputSchema: {
|
|
31343
32017
|
subsystem: import_zod11.z.string().describe("The L1 subsystem id to update"),
|
|
31344
|
-
publicInterfaces: import_zod11.z.array(
|
|
31345
|
-
type: import_zod11.z.enum(["REST", "GraphQL", "MessageBus", "RPC", "Custom"]),
|
|
31346
|
-
details: import_zod11.z.string(),
|
|
31347
|
-
component: import_zod11.z.string().optional().describe("The L2 component id that realizes this interface"),
|
|
31348
|
-
interface: import_zod11.z.string().optional().describe("Optional L3 interface id on that component"),
|
|
31349
|
-
consumers: import_zod11.z.array(import_zod11.z.string()).optional().describe("The subsystem ids this surface is published to. Absent, any subsystem may depend on the component; present, only these may \u2014 when every entry publishing the component names consumers, the union of the lists is the whole set, and a dependency from any other subsystem is CROSS_SUBSYSTEM_UNLISTED_CONSUMER. Every id must name a subsystem of the tree (PUBLIC_INTERFACE_UNKNOWN_CONSUMER).")
|
|
31350
|
-
}).strict()).describe("The full replacement list of public interfaces for this subsystem")
|
|
32018
|
+
publicInterfaces: import_zod11.z.array(publicInterfaceItemInput).describe("The full replacement list of the subsystem's export table: own items and re-exports")
|
|
31351
32019
|
},
|
|
31352
32020
|
outputSchema: specChangeReportOutput
|
|
31353
32021
|
},
|
|
@@ -31613,7 +32281,7 @@ ${renderChangeReport(report2)}`,
|
|
|
31613
32281
|
}).strict().optional().describe("Typed acknowledgment of a real caller OUTSIDE the modeled narrative graph. Unused-detection seeds the method as an entrypoint so reachability propagates through its narrative (unlike lint.allow); a method the internal walk already reaches is flagged stale (INVOKED_BY_REDUNDANT). Prefer a `register` narrative step when the wiring is internal."),
|
|
31614
32282
|
findings: import_zod11.z.array(import_zod11.z.object({
|
|
31615
32283
|
code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
|
|
31616
|
-
severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
|
|
32284
|
+
severity: import_zod11.z.enum(["error", "warning", "notice"]).describe("Default severity, before project severity overrides and draft-context downgrades. A notice is reported but never fails the gate"),
|
|
31617
32285
|
summary: import_zod11.z.string().describe("One line saying what the finding means")
|
|
31618
32286
|
}).strict()).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
|
|
31619
32287
|
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
|
|
@@ -32057,9 +32725,9 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
32057
32725
|
{
|
|
32058
32726
|
description: "Update/patch an existing SDD specification (subsystem, component, interface, implementation, or type) using a granular delta. Updates fields, appends/merges array elements, or inserts/deletes narrative steps. Answers with exactly what changed, and with every path the delta named that the write did not act on. Pass dryRun to be told what it would do without writing it.",
|
|
32059
32727
|
inputSchema: {
|
|
32060
|
-
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project
|
|
32728
|
+
kind: import_zod11.z.enum(["system", "subsystem", "component", "interface", "implementation", "type"]).describe("The spec kind to update (system = the singleton L0 \u2014 vision, boundaries, globalRequirements, databases, and publicInterfaces: the project export table, each entry a re-export {from, component, interface, typeDef, as, name, type, details, audience: project|department|instance|partner|external} \u2014 from alone re-exports everything a subsystem exports, and a legacy {id, subsystem} reads as {as, from})"),
|
|
32061
32729
|
id: import_zod11.z.string().describe("The ID of the spec to update (namespaced if needed)"),
|
|
32062
|
-
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
32730
|
+
delta: import_zod11.z.record(import_zod11.z.any()).describe(`The partial fields to merge into the spec. ARRAYS UPSERT, they do not replace: an array whose elements carry an identity is merged element-by-element, so a delta naming ONE element leaves the others intact. Identity is "name" or "id" by default, and per field: dispatch by "capability", a listener's mounts by "portal", lifecycle by phase+component+method, emits/subscribesTo by topic+event, trustedLinks by "subsystem", a subsystem's publicInterfaces by component+interface (or type+details for an entry not yet bound to a component, and by from+item+interface+as for a re-export or a type export), invariants and patterns by "id", lint.allow by "code" AND "at" (an allow covers one occurrence, so several may share a code on one spec), an interface method's findings by "code", boundaries by "name", globalRequirements by "description", switch cases by "value", try catches by "error". Identity merging applies at EVERY depth, including an array INSIDE an element (a method's params, a step's catches). Add "action: 'delete'" (or "remove: true") alongside that identity to REMOVE an element \u2014 including a stale lint allow. Arrays of plain STRINGS (owns, dependsOn, guarantees) carry no per-element identity and are replaced wholesale; pass [] to clear any array outright. To REMOVE an optional field entirely, list it in "unset": e.g. {"unset": ["basePath", "variant"]} \u2014 passing null/undefined means "no change" (they are skipped), and writing "" would leave the field present but empty, which is a different and usually wrong spec. Unsetting a required field is refused by schema validation, which names it. For narrative steps, match by "stepNumber" and use "action: 'insert'" (shifts subsequent steps up) or "action: 'delete'" (shifts subsequent steps down and removes it). Step entries apply in ASCENDING stepNumber order, each against the numbering the earlier entries of the SAME delta left behind \u2014 delete step 3 and step 7 becomes step 6 \u2014 so prefer labels, and restate the step's "label" or "description" on a delete to have it checked against the step actually addressed. Renumbering RELOCATES every flow jump field (onTrueStep/onFalseStep/cases.step/defaultStep/endStep/catches.step/finallyStep/toStep) in the same narrative. A delete is REJECTED when the narrative has no such step, when a jump still targets it (retarget the referrers first), when a restated label/description does not match, or when it is a loop/try/parallel header whose body would be left standing (retype the header first to dissolve the region, then delete it). Changing a step's "type" REBUILDS it for the new type: its description and label are kept and every field the new type cannot carry is dropped (returned as a NOTICE); a delta that retypes AND sets such a field is refused. A step delta is also refused when it carries a marker the merge does not recognise: a non-boolean "remove", an "action" that is neither "insert" nor "delete", a "captureJumps" outside an insert, or no "stepNumber" to address. Inserting AT a jump target relocates those jumps past the inserted step by default (a NOTICE is returned) \u2014 add "captureJumps": true on the inserted step to retarget entry jumps onto it (loop/try endStep region tails always relocate with the body and are never captured). Every jump field has a "*Label" twin (toLabel, onFalseLabel, endLabel, \u2026, and "label" on a cases/catches entry) resolved against step labels AFTER the merge, so a delta may anchor on a label only pre-existing steps carry; a label the delta supplies REPLACES the stored number it twins, while setting the number and its label together in one delta is refused as a contradiction. Reference ids in deltas may use LOCAL names \u2014 they are qualified against the spec's namespace exactly as the loader would. Per-spec lint suppression: set "lint: { allow: [{ code, at, covers, reason }] }" to silence a WARNING or NOTICE code on this spec only (errors always surface; stale allows are flagged). An allow covers EXACTLY the occurrence it names: "at" is the site the finding names (a contract method, an import edge "from -> to", a declared edge "component -> target") and is REQUIRED for a code whose findings report one, while a finding that reports no site is covered only by an allow that names none; "covers" lists the units an aggregating finding reports, and the allow silences it only when every one is listed. This delta is deliberately OPEN below its top level \u2014 the shapes nest further than a schema here should restate \u2014 so a key that is not a field at its depth is not refused, it is NAMED BACK under NO EFFECT in the answer, together with any value the spec already held and any "unset" that removed nothing. Read that list: it is where a nested typo shows up.`),
|
|
32063
32731
|
dryRun: import_zod11.z.boolean().optional().describe("Ask what this delta WOULD do instead of doing it. The whole write runs, the candidate gate included, and the answer is the change report it would have produced \u2014 marked DRY RUN, with nothing stamped and not one byte of the stored file moved. Use it before a delta that renumbers a long narrative.")
|
|
32064
32732
|
},
|
|
32065
32733
|
outputSchema: specChangeReportOutput
|
|
@@ -33331,8 +33999,10 @@ async function executeInit(projectName, projectConfig, guidePlan) {
|
|
|
33331
33999
|
logger.blank();
|
|
33332
34000
|
}
|
|
33333
34001
|
function buildProjectConfig(name, targets, now, projectType) {
|
|
34002
|
+
const id = effectiveProjectId({ name });
|
|
33334
34003
|
return {
|
|
33335
34004
|
schemaVersion: "1.0.0",
|
|
34005
|
+
...id !== null ? { id } : {},
|
|
33336
34006
|
name,
|
|
33337
34007
|
projectType,
|
|
33338
34008
|
targets,
|
|
@@ -33534,7 +34204,7 @@ async function runGenerate(options = {}) {
|
|
|
33534
34204
|
logger.blank();
|
|
33535
34205
|
logger.info(`\u21B3 Chained subproject "${child.subsystemId}" \u2014 generating its layer in ${path36.relative(getProjectRoot(), child.dir) || "."}/`);
|
|
33536
34206
|
await runWithProjectRoot(child.dir, async () => {
|
|
33537
|
-
ensureProjectInitialized(child.subsystemId);
|
|
34207
|
+
ensureProjectInitialized(child.subsystemId, child.subsystemId);
|
|
33538
34208
|
await runGenerate(options);
|
|
33539
34209
|
});
|
|
33540
34210
|
}
|
|
@@ -33640,6 +34310,7 @@ init_logger();
|
|
|
33640
34310
|
init_defaults();
|
|
33641
34311
|
var path37 = __toESM(require("path"));
|
|
33642
34312
|
init_core();
|
|
34313
|
+
init_project();
|
|
33643
34314
|
init_lock();
|
|
33644
34315
|
init_fs();
|
|
33645
34316
|
init_validation();
|
|
@@ -33716,6 +34387,8 @@ async function runLock(options = {}, gate) {
|
|
|
33716
34387
|
specPathsInScope(options.subsystem).map((p) => path37.relative(root, p).split(path37.sep).join("/"))
|
|
33717
34388
|
)
|
|
33718
34389
|
} : void 0;
|
|
34390
|
+
const config = loadProjectConfig();
|
|
34391
|
+
const projectId = config ? effectiveProjectId(config) : null;
|
|
33719
34392
|
const record2 = {
|
|
33720
34393
|
stateId: computeGateStateId(),
|
|
33721
34394
|
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -33728,6 +34401,7 @@ async function runLock(options = {}, gate) {
|
|
|
33728
34401
|
notices: gate ? gate.issues.filter((i) => i.severity === "notice").length : 0
|
|
33729
34402
|
},
|
|
33730
34403
|
status: "ready",
|
|
34404
|
+
...projectId !== null ? { projectId } : {},
|
|
33731
34405
|
specs: captureApprovedSpecs(root, scope),
|
|
33732
34406
|
children: currentChildPins(loadSubsystemSpecs(), root)
|
|
33733
34407
|
};
|
|
@@ -36106,6 +36780,7 @@ init_errors();
|
|
|
36106
36780
|
var crypto11 = __toESM(require("crypto"));
|
|
36107
36781
|
init_fs();
|
|
36108
36782
|
init_defaults();
|
|
36783
|
+
init_project();
|
|
36109
36784
|
|
|
36110
36785
|
// src/server/auth.ts
|
|
36111
36786
|
var crypto9 = __toESM(require("crypto"));
|
|
@@ -36440,14 +37115,14 @@ function registrySetStatus(dataDir, id, status2) {
|
|
|
36440
37115
|
}
|
|
36441
37116
|
function registryRemapUnitReferences(dataDir, remap, removedScopeIds) {
|
|
36442
37117
|
if (remap.length === 0 && removedScopeIds.length === 0) return;
|
|
36443
|
-
const
|
|
37118
|
+
const lookup2 = new Map(remap.map((r) => [r.oldId, r.newId]));
|
|
36444
37119
|
const removed = new Set(removedScopeIds);
|
|
36445
37120
|
const records = loadStore(dataDir);
|
|
36446
37121
|
let changed = false;
|
|
36447
37122
|
const rewritten = records.map((record2) => {
|
|
36448
37123
|
const next = { ...record2 };
|
|
36449
37124
|
if (next.unitId) {
|
|
36450
|
-
const moved =
|
|
37125
|
+
const moved = lookup2.get(next.unitId);
|
|
36451
37126
|
if (moved) {
|
|
36452
37127
|
next.unitId = moved;
|
|
36453
37128
|
changed = true;
|
|
@@ -36457,7 +37132,7 @@ function registryRemapUnitReferences(dataDir, remap, removedScopeIds) {
|
|
|
36457
37132
|
}
|
|
36458
37133
|
}
|
|
36459
37134
|
if (next.roleBindings && next.roleBindings.length > 0) {
|
|
36460
|
-
const bindings = next.roleBindings.filter((b) => !(b.scopeId && removed.has(b.scopeId))).map((b) => b.scopeId &&
|
|
37135
|
+
const bindings = next.roleBindings.filter((b) => !(b.scopeId && removed.has(b.scopeId))).map((b) => b.scopeId && lookup2.has(b.scopeId) ? { ...b, scopeId: lookup2.get(b.scopeId) } : b);
|
|
36461
37136
|
if (bindings.length !== next.roleBindings.length || bindings.some((b, i) => b.scopeId !== next.roleBindings?.[i]?.scopeId)) {
|
|
36462
37137
|
next.roleBindings = bindings;
|
|
36463
37138
|
changed = true;
|
|
@@ -37337,11 +38012,11 @@ function registryRemove(dataDir, assignmentId) {
|
|
|
37337
38012
|
}
|
|
37338
38013
|
function registryRemapScope(dataDir, remap) {
|
|
37339
38014
|
if (remap.length === 0) return;
|
|
37340
|
-
const
|
|
38015
|
+
const lookup2 = new Map(remap.map((r) => [r.oldId, r.newId]));
|
|
37341
38016
|
const assignments = load4(dataDir);
|
|
37342
38017
|
let changed = false;
|
|
37343
38018
|
const rewritten = assignments.map((a) => {
|
|
37344
|
-
const next = a.scopeId ?
|
|
38019
|
+
const next = a.scopeId ? lookup2.get(a.scopeId) : void 0;
|
|
37345
38020
|
if (!next) return a;
|
|
37346
38021
|
changed = true;
|
|
37347
38022
|
return { ...a, scopeId: next };
|
|
@@ -38109,12 +38784,19 @@ function systemBody(system, mermaid, diagramUrl) {
|
|
|
38109
38784
|
if (mermaid) parts.push("```mermaid", mermaid.trim(), "```", "");
|
|
38110
38785
|
return parts.join("\n");
|
|
38111
38786
|
}
|
|
38787
|
+
function publicInterfaceLine(p) {
|
|
38788
|
+
const item = p.typeDef ? `type \`${p.typeDef}\`` : p.component ? `\`${p.component}\`` : "everything";
|
|
38789
|
+
const name = p.as ? ` as \`${p.as}\`` : "";
|
|
38790
|
+
if (p.from) return `- re-exports ${item}${name} from \`${p.from}\`${p.details ? ` \u2014 ${p.details}` : ""}`;
|
|
38791
|
+
if (p.typeDef) return `- ${item}${name}`;
|
|
38792
|
+
return `- **${p.type}** via \`${p.component ?? "?"}\`${name} \u2014 ${p.details}`;
|
|
38793
|
+
}
|
|
38112
38794
|
function subsystemBody(sub) {
|
|
38113
38795
|
const parts = [`# ${sub.name || sub.id}`, ""];
|
|
38114
38796
|
if (sub.description) parts.push(sub.description, "");
|
|
38115
38797
|
if (sub.publicInterfaces?.length) {
|
|
38116
38798
|
parts.push("## Public surface");
|
|
38117
|
-
for (const p of sub.publicInterfaces) parts.push(
|
|
38799
|
+
for (const p of sub.publicInterfaces) parts.push(publicInterfaceLine(p));
|
|
38118
38800
|
parts.push("");
|
|
38119
38801
|
}
|
|
38120
38802
|
return parts.join("\n");
|
|
@@ -38510,6 +39192,8 @@ function executeApprovedLock(cfg, projectId, approver, subproject) {
|
|
|
38510
39192
|
const stateId = computeGateStateId();
|
|
38511
39193
|
const specs = captureApprovedSpecs();
|
|
38512
39194
|
const children = currentChildPins(loadSubsystemSpecs());
|
|
39195
|
+
const config = loadProjectConfig();
|
|
39196
|
+
const projectId2 = config ? effectiveProjectId(config) : null;
|
|
38513
39197
|
const record2 = {
|
|
38514
39198
|
stateId,
|
|
38515
39199
|
lockedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -38522,11 +39206,12 @@ function executeApprovedLock(cfg, projectId, approver, subproject) {
|
|
|
38522
39206
|
notices: result.issues.filter((i) => i.severity === "notice").length
|
|
38523
39207
|
},
|
|
38524
39208
|
status: "ready",
|
|
39209
|
+
...projectId2 !== null ? { projectId: projectId2 } : {},
|
|
38525
39210
|
specs,
|
|
38526
39211
|
children
|
|
38527
39212
|
};
|
|
38528
39213
|
writeLockRecord(record2);
|
|
38529
|
-
const publish2 = publish(`wairon lock: ${
|
|
39214
|
+
const publish2 = publish(`wairon lock: ${projectId2} @ ${stateId}`);
|
|
38530
39215
|
if (!publish2.published) return record2;
|
|
38531
39216
|
const published = {
|
|
38532
39217
|
...record2,
|
|
@@ -41036,8 +41721,6 @@ function applyEndpointOverrides(config) {
|
|
|
41036
41721
|
|
|
41037
41722
|
// src/server/landscape.ts
|
|
41038
41723
|
init_fs();
|
|
41039
|
-
init_yaml();
|
|
41040
|
-
init_paths();
|
|
41041
41724
|
|
|
41042
41725
|
// src/server/relations.ts
|
|
41043
41726
|
var fs45 = __toESM(require("fs"));
|
|
@@ -41390,6 +42073,7 @@ function openApiIndexDocument(specs) {
|
|
|
41390
42073
|
}
|
|
41391
42074
|
|
|
41392
42075
|
// src/server/landscape.ts
|
|
42076
|
+
init_models();
|
|
41393
42077
|
var PROJECT_ADMIN_CAPABILITY3 = "project:admin";
|
|
41394
42078
|
var PROJECT_READ_CAPABILITY3 = "project:read";
|
|
41395
42079
|
var PROJECT_WRITE_CAPABILITY3 = "project:write";
|
|
@@ -41460,66 +42144,36 @@ function tryAppendAudit4(cfg, event) {
|
|
|
41460
42144
|
);
|
|
41461
42145
|
}
|
|
41462
42146
|
}
|
|
41463
|
-
function
|
|
41464
|
-
|
|
41465
|
-
if (typeof v === "string" && v.trim().length > 0) return v;
|
|
41466
|
-
}
|
|
41467
|
-
return void 0;
|
|
42147
|
+
function endpointPath(m) {
|
|
42148
|
+
return m.endpoint?.transport === "HTTP" ? m.endpoint.path : void 0;
|
|
41468
42149
|
}
|
|
41469
|
-
function
|
|
41470
|
-
|
|
41471
|
-
const
|
|
41472
|
-
|
|
41473
|
-
|
|
41474
|
-
if (item.trim().length > 0) out.push(item);
|
|
41475
|
-
} else if (item && typeof item === "object") {
|
|
41476
|
-
const rec = item;
|
|
41477
|
-
const name = firstString(rec.name, rec.id);
|
|
41478
|
-
if (name) out.push(name);
|
|
42150
|
+
function referencedTypeNames(methods) {
|
|
42151
|
+
const names = /* @__PURE__ */ new Set();
|
|
42152
|
+
for (const m of methods) {
|
|
42153
|
+
for (const ref of methodTypeRefs(m)) {
|
|
42154
|
+
if (!BUILTIN_TYPES.has(ref.toLowerCase())) names.add(ref.split(/::|\./).pop());
|
|
41479
42155
|
}
|
|
41480
42156
|
}
|
|
41481
|
-
return
|
|
42157
|
+
return [...names];
|
|
41482
42158
|
}
|
|
41483
|
-
function
|
|
41484
|
-
const raw = readYamlFile(AI_PATHS.specsSystem());
|
|
41485
|
-
const list2 = raw?.publicInterfaces;
|
|
41486
|
-
return Array.isArray(list2) ? list2 : [];
|
|
41487
|
-
}
|
|
41488
|
-
function resolveSubsystemInterface(entry, subsystems) {
|
|
41489
|
-
const subId = firstString(entry.subsystem);
|
|
41490
|
-
const interfaceId = firstString(entry.interface);
|
|
41491
|
-
const componentId = firstString(entry.component);
|
|
41492
|
-
const pools = subId ? subsystems.filter((s) => s.id === subId).map((s) => s.publicInterfaces) : subsystems.map((s) => s.publicInterfaces);
|
|
41493
|
-
for (const pool of pools) {
|
|
41494
|
-
const match = pool.find(
|
|
41495
|
-
(pi) => interfaceId !== void 0 && pi.interface === interfaceId || componentId !== void 0 && pi.component === componentId
|
|
41496
|
-
);
|
|
41497
|
-
if (match) return match;
|
|
41498
|
-
}
|
|
41499
|
-
return void 0;
|
|
41500
|
-
}
|
|
41501
|
-
function buildRedactedSummaries(rawList, subsystems) {
|
|
42159
|
+
function buildRedactedSummaries(table, interfaces) {
|
|
41502
42160
|
const out = [];
|
|
41503
|
-
for (const entry of
|
|
41504
|
-
if (
|
|
41505
|
-
const
|
|
41506
|
-
if (!id) continue;
|
|
41507
|
-
const matched = resolveSubsystemInterface(entry, subsystems);
|
|
42161
|
+
for (const entry of table.entries) {
|
|
42162
|
+
if (entry.kind !== "component") continue;
|
|
42163
|
+
const methods = interfaces.filter((i) => i.component === entry.component && (!entry.interface || i.id === entry.interface)).flatMap((i) => i.methods);
|
|
41508
42164
|
const summary = {
|
|
41509
|
-
id,
|
|
41510
|
-
name:
|
|
41511
|
-
type:
|
|
41512
|
-
audience:
|
|
41513
|
-
methods:
|
|
41514
|
-
details:
|
|
41515
|
-
};
|
|
41516
|
-
|
|
41517
|
-
if (
|
|
41518
|
-
const
|
|
41519
|
-
if (stability) summary.stability = stability;
|
|
41520
|
-
const endpoints = toNameList(entry.endpoints);
|
|
42165
|
+
id: entry.publicName,
|
|
42166
|
+
name: entry.name ?? entry.publicName,
|
|
42167
|
+
type: entry.type ?? "Custom",
|
|
42168
|
+
audience: entry.audience ?? "instance",
|
|
42169
|
+
methods: methods.map((m) => m.name),
|
|
42170
|
+
details: entry.details ?? ""
|
|
42171
|
+
};
|
|
42172
|
+
if (entry.version) summary.version = entry.version;
|
|
42173
|
+
if (entry.stability) summary.stability = entry.stability;
|
|
42174
|
+
const endpoints = methods.map(endpointPath).filter((e) => !!e);
|
|
41521
42175
|
if (endpoints.length > 0) summary.endpoints = endpoints;
|
|
41522
|
-
const publicTypes =
|
|
42176
|
+
const publicTypes = referencedTypeNames(methods);
|
|
41523
42177
|
if (publicTypes.length > 0) summary.publicTypes = publicTypes;
|
|
41524
42178
|
out.push(summary);
|
|
41525
42179
|
}
|
|
@@ -41680,9 +42334,8 @@ function refreshPublicSurface(cfg, credential, projectId) {
|
|
|
41680
42334
|
const snapshot = runWithProjectRoot(root, () => {
|
|
41681
42335
|
const stateId = computeStateId();
|
|
41682
42336
|
const system = loadSystemSpec();
|
|
41683
|
-
const
|
|
41684
|
-
const
|
|
41685
|
-
const interfaces = buildRedactedSummaries(rawInterfaces, subsystems);
|
|
42337
|
+
const exports2 = resolveProjectExports();
|
|
42338
|
+
const interfaces = buildRedactedSummaries(exports2, loadInterfaceSpecs());
|
|
41686
42339
|
return {
|
|
41687
42340
|
projectId,
|
|
41688
42341
|
stateId: stateIdToString(stateId),
|
|
@@ -42120,14 +42773,14 @@ function migratePermissionModel(dataDir, apply) {
|
|
|
42120
42773
|
}
|
|
42121
42774
|
}
|
|
42122
42775
|
if (apply) {
|
|
42123
|
-
const
|
|
42776
|
+
const lookup2 = new Map(remap.map((r) => [r.oldId, r.newId]));
|
|
42124
42777
|
for (const unit of units) {
|
|
42125
|
-
unit.id =
|
|
42126
|
-
if (unit.parentId !== void 0) unit.parentId =
|
|
42127
|
-
if (unit.exposeTo) unit.exposeTo = unit.exposeTo.map((e) =>
|
|
42778
|
+
unit.id = lookup2.get(unit.id) ?? unit.id;
|
|
42779
|
+
if (unit.parentId !== void 0) unit.parentId = lookup2.get(unit.parentId) ?? unit.parentId;
|
|
42780
|
+
if (unit.exposeTo) unit.exposeTo = unit.exposeTo.map((e) => lookup2.get(e) ?? e);
|
|
42128
42781
|
}
|
|
42129
42782
|
for (const placement of org.placements ?? []) {
|
|
42130
|
-
placement.unitId =
|
|
42783
|
+
placement.unitId = lookup2.get(placement.unitId) ?? placement.unitId;
|
|
42131
42784
|
}
|
|
42132
42785
|
writeRaw(orgPath, { units, placements: org.placements ?? [] });
|
|
42133
42786
|
if (remap.length > 0) {
|