@wairon/cli 5.1.1-dev.106 → 5.1.1-dev.107
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 +2468 -574
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +464 -42
- package/dist/index.js +1884 -281
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +9 -9
- package/dist/templates/skills/sdd-delegate.md +1 -1
- package/docs/cli.md +127 -14
- package/docs/network.md +11 -6
- package/package.json +1 -1
- package/schemas/design-export-2.json +6 -0
package/dist/index.js
CHANGED
|
@@ -3547,6 +3547,7 @@ __export(src_exports, {
|
|
|
3547
3547
|
SurfaceSnapshotSchema: () => SurfaceSnapshotSchema,
|
|
3548
3548
|
SurfaceTypeDefSchema: () => SurfaceTypeDefSchema,
|
|
3549
3549
|
SurfaceTypeExportSchema: () => SurfaceTypeExportSchema,
|
|
3550
|
+
SurfaceUnresolvedExportSchema: () => SurfaceUnresolvedExportSchema,
|
|
3550
3551
|
SwitchCaseSchema: () => SwitchCaseSchema,
|
|
3551
3552
|
SystemPublicInterfaceSchema: () => SystemPublicInterfaceSchema,
|
|
3552
3553
|
SystemSpecSchema: () => SystemSpecSchema,
|
|
@@ -3580,12 +3581,14 @@ __export(src_exports, {
|
|
|
3580
3581
|
approvalKeyIn: () => approvalKeyIn,
|
|
3581
3582
|
approvalRecord: () => approvalRecord,
|
|
3582
3583
|
approvalVerdict: () => approvalVerdict,
|
|
3584
|
+
approvedStatuses: () => approvedStatuses,
|
|
3583
3585
|
assertContainedProjectPath: () => assertContainedProjectPath,
|
|
3584
3586
|
assertProjectInitialized: () => assertProjectInitialized,
|
|
3585
3587
|
attachRetiredMounts: () => attachRetiredMounts,
|
|
3586
3588
|
backendCommand: () => backendCommand,
|
|
3587
3589
|
backfillChainedSubprojectConfigs: () => backfillChainedSubprojectConfigs,
|
|
3588
3590
|
backupBeside: () => backupBeside,
|
|
3591
|
+
bindDeclaredExternal: () => bindDeclaredExternal,
|
|
3589
3592
|
bodySitesOf: () => bodySitesOf,
|
|
3590
3593
|
buildCanvasDataModel: () => buildCanvasDataModel,
|
|
3591
3594
|
buildProjectGraph: () => buildProjectGraph,
|
|
@@ -3597,6 +3600,7 @@ __export(src_exports, {
|
|
|
3597
3600
|
canonicalTypeRef: () => canonicalTypeRef,
|
|
3598
3601
|
canonicalTypeText: () => canonicalTypeText,
|
|
3599
3602
|
captureApprovedSpecs: () => captureApprovedSpecs,
|
|
3603
|
+
carriedFactChanges: () => carriedFactChanges,
|
|
3600
3604
|
carryRetiredReachForms: () => carryRetiredReachForms,
|
|
3601
3605
|
checkDeclarativePack: () => checkDeclarativePack,
|
|
3602
3606
|
clearLoaderIssues: () => clearLoaderIssues,
|
|
@@ -3608,6 +3612,7 @@ __export(src_exports, {
|
|
|
3608
3612
|
composeRuleSequence: () => composeRuleSequence,
|
|
3609
3613
|
computeGateStateId: () => computeGateStateId,
|
|
3610
3614
|
computeOwnDesignId: () => computeOwnDesignStateId,
|
|
3615
|
+
computeOwnDesignIdAsRecorded: () => computeOwnDesignStateIdAsRecorded,
|
|
3611
3616
|
computeOwnStateId: () => computeOwnStateId,
|
|
3612
3617
|
computeStateId: () => computeStateId,
|
|
3613
3618
|
computeStateIdAt: () => computeStateIdAt,
|
|
@@ -3711,6 +3716,7 @@ __export(src_exports, {
|
|
|
3711
3716
|
injectGuide: () => injectGuide,
|
|
3712
3717
|
installPackFromDirectory: () => installPackFromDirectory,
|
|
3713
3718
|
interfaceCanonicalTypes: () => interfaceCanonicalTypes,
|
|
3719
|
+
interfaceDesignView: () => interfaceDesignView,
|
|
3714
3720
|
interfaceGenericParameters: () => interfaceGenericParameters,
|
|
3715
3721
|
internalizeMember: () => internalizeMember,
|
|
3716
3722
|
isDraftSubsystem: () => isDraftSubsystem,
|
|
@@ -3730,6 +3736,7 @@ __export(src_exports, {
|
|
|
3730
3736
|
languageOfSourcePath: () => languageOfSourcePath,
|
|
3731
3737
|
lineEndingFor: () => lineEndingFor,
|
|
3732
3738
|
listDirectChainedSubprojects: () => listDirectChainedSubprojects,
|
|
3739
|
+
listExternalConsumers: () => listConsumers,
|
|
3733
3740
|
listExternals: () => listExternals2,
|
|
3734
3741
|
listFiles: () => listFiles,
|
|
3735
3742
|
listFilesRecursive: () => listFilesRecursive,
|
|
@@ -3831,6 +3838,7 @@ __export(src_exports, {
|
|
|
3831
3838
|
removeSnapshot: () => removeSnapshot2,
|
|
3832
3839
|
renameAlias: () => renameAlias,
|
|
3833
3840
|
renameComponent: () => renameComponent,
|
|
3841
|
+
renameField: () => renameField,
|
|
3834
3842
|
renameId: () => renameProjectId,
|
|
3835
3843
|
renameMethod: () => renameMethod,
|
|
3836
3844
|
renameType: () => renameType,
|
|
@@ -3845,6 +3853,7 @@ __export(src_exports, {
|
|
|
3845
3853
|
resolveChainingParent: () => resolveChainingParent,
|
|
3846
3854
|
resolveDomains: () => resolveDomains2,
|
|
3847
3855
|
resolveExpectedOutputPaths: () => resolveExpectedOutputPaths,
|
|
3856
|
+
resolveExternalCandidate: () => resolveCandidate,
|
|
3848
3857
|
resolveExternals: () => resolveDeclared,
|
|
3849
3858
|
resolveImport: () => resolveImport,
|
|
3850
3859
|
resolveInstalledPack: () => resolveInstalledPack,
|
|
@@ -3873,6 +3882,7 @@ __export(src_exports, {
|
|
|
3873
3882
|
setExecutionTier: () => setExecutionTier,
|
|
3874
3883
|
setId: () => setProjectId,
|
|
3875
3884
|
setLogLevel: () => setLogLevel,
|
|
3885
|
+
setNetwork: () => setNetwork,
|
|
3876
3886
|
setProjectRoot: () => setProjectRoot,
|
|
3877
3887
|
setProjectType: () => setProjectType,
|
|
3878
3888
|
settledSpecPaths: () => settledSpecPaths,
|
|
@@ -3886,6 +3896,7 @@ __export(src_exports, {
|
|
|
3886
3896
|
stepGraph: () => stepGraph,
|
|
3887
3897
|
storedMethodSignature: () => storedMethodSignature,
|
|
3888
3898
|
storedTypeMethod: () => storedTypeMethod,
|
|
3899
|
+
subsystemDesignView: () => subsystemDesignView,
|
|
3889
3900
|
syncContextFiles: () => syncContextFiles,
|
|
3890
3901
|
technologyName: () => technologyName,
|
|
3891
3902
|
technologyTokens: () => technologyTokens,
|
|
@@ -3899,6 +3910,7 @@ __export(src_exports, {
|
|
|
3899
3910
|
typeDialectFor: () => typeDialectFor,
|
|
3900
3911
|
typeGenericParameters: () => typeGenericParameters,
|
|
3901
3912
|
typeIsMany: () => typeIsMany,
|
|
3913
|
+
typeMappingFor: () => typeMappingFor,
|
|
3902
3914
|
typeMatchesRef: () => typeMatchesRef,
|
|
3903
3915
|
typeNamedRefs: () => typeNamedRefs,
|
|
3904
3916
|
typeProblemEnumProposal: () => typeProblemEnumProposal,
|
|
@@ -4273,7 +4285,7 @@ function defaultTargetConfig(type) {
|
|
|
4273
4285
|
enabled: true
|
|
4274
4286
|
};
|
|
4275
4287
|
}
|
|
4276
|
-
var WAIRON_VERSION = "5.1.1-dev.
|
|
4288
|
+
var WAIRON_VERSION = "5.1.1-dev.107";
|
|
4277
4289
|
var GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
4278
4290
|
var ARCHITECT_AGENT_ID = "agent-architect";
|
|
4279
4291
|
var ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -5390,12 +5402,12 @@ function declaredPackNames(config) {
|
|
|
5390
5402
|
function declaredProfileIds(config) {
|
|
5391
5403
|
return [...new Set(config.profileSelection?.profileIds ?? [])];
|
|
5392
5404
|
}
|
|
5393
|
-
function withPack(config, entry,
|
|
5405
|
+
function withPack(config, entry, remove3 = false) {
|
|
5394
5406
|
const packs = config.extensions?.packs ?? [];
|
|
5395
5407
|
const same = (e) => typeof entry === "string" ? e === entry : typeof e !== "string" && e.name === entry.name;
|
|
5396
5408
|
const others = packs.filter((e) => !same(e));
|
|
5397
5409
|
let next;
|
|
5398
|
-
if (
|
|
5410
|
+
if (remove3) next = others;
|
|
5399
5411
|
else if (typeof entry === "string") next = packs.includes(entry) ? packs : [...packs, entry];
|
|
5400
5412
|
else next = [...others, entry];
|
|
5401
5413
|
return { ...config, extensions: { useGlobalPacks: false, ...config.extensions, packs: next } };
|
|
@@ -6045,8 +6057,8 @@ var DocumentReconciler = class {
|
|
|
6045
6057
|
}
|
|
6046
6058
|
mapping(map, target, at, replace2) {
|
|
6047
6059
|
const written = (pair) => String(YAML.isScalar(pair.key) ? pair.key.value : pair.key);
|
|
6048
|
-
const
|
|
6049
|
-
const keyOf = (pair) =>
|
|
6060
|
+
const renamed2 = this.renamedKeys(map, target, written);
|
|
6061
|
+
const keyOf = (pair) => renamed2.get(pair) ?? written(pair);
|
|
6050
6062
|
const kept = map.items.filter((pair) => target[keyOf(pair)] !== void 0);
|
|
6051
6063
|
if (kept.length === 0) {
|
|
6052
6064
|
replace2(target);
|
|
@@ -6056,7 +6068,7 @@ var DocumentReconciler = class {
|
|
|
6056
6068
|
const key2 = keyOf(pair);
|
|
6057
6069
|
const span = this.pairSpan(pair);
|
|
6058
6070
|
const keyRange = pair.key.range;
|
|
6059
|
-
if (
|
|
6071
|
+
if (renamed2.has(pair) && keyRange) this.splices.push({ start: keyRange[0], end: keyRange[1], text: key2, rank: 0 });
|
|
6060
6072
|
if (target[key2] === void 0) {
|
|
6061
6073
|
this.splices.push({ start: span.start, end: span.end, text: "", rank: 0 });
|
|
6062
6074
|
continue;
|
|
@@ -6294,6 +6306,13 @@ function packsOf(config) {
|
|
|
6294
6306
|
function withPacks(config, packs) {
|
|
6295
6307
|
return { ...config, extensions: { ...config.extensions, packs, useGlobalPacks: effectiveUseGlobalPacks(config) } };
|
|
6296
6308
|
}
|
|
6309
|
+
function networkReads(stored, value) {
|
|
6310
|
+
if (value === null || stored === void 0) return value === null && stored === void 0;
|
|
6311
|
+
return (stored.description ?? "") === (value.description ?? "");
|
|
6312
|
+
}
|
|
6313
|
+
function networkValue(network) {
|
|
6314
|
+
return network.description ? { description: network.description } : true;
|
|
6315
|
+
}
|
|
6297
6316
|
function registryOver(store, root) {
|
|
6298
6317
|
const current2 = () => {
|
|
6299
6318
|
const config = store.read();
|
|
@@ -6499,6 +6518,18 @@ function registryOver(store, root) {
|
|
|
6499
6518
|
store.write(partConfig(partOf, config));
|
|
6500
6519
|
return true;
|
|
6501
6520
|
},
|
|
6521
|
+
setNetwork(network) {
|
|
6522
|
+
const config = current2();
|
|
6523
|
+
if (config.partOf !== void 0) {
|
|
6524
|
+
throw new WaironError(
|
|
6525
|
+
`Refusing to declare a network at ${root}: it is a part of "${config.partOf.project}", and a part declares no network \u2014 the project that declares one encloses it. Declare it on that project.`
|
|
6526
|
+
);
|
|
6527
|
+
}
|
|
6528
|
+
if (networkReads(config.network, network)) return false;
|
|
6529
|
+
const { network: _dropped, ...rest } = config;
|
|
6530
|
+
save(network === null ? rest : { ...rest, network: networkValue(network) });
|
|
6531
|
+
return true;
|
|
6532
|
+
},
|
|
6502
6533
|
removeMember(alias) {
|
|
6503
6534
|
const config = current2();
|
|
6504
6535
|
const members = config.members;
|
|
@@ -6738,6 +6769,9 @@ function projectConfigRepositoryOver(adapter, rootDir) {
|
|
|
6738
6769
|
setPartOf(partOf) {
|
|
6739
6770
|
return registry.setPartOf(partOf);
|
|
6740
6771
|
},
|
|
6772
|
+
setNetwork(network) {
|
|
6773
|
+
return registry.setNetwork(network);
|
|
6774
|
+
},
|
|
6741
6775
|
removeMember(alias) {
|
|
6742
6776
|
return registry.removeMember(alias);
|
|
6743
6777
|
},
|
|
@@ -6832,6 +6866,9 @@ var projectConfigRepository = {
|
|
|
6832
6866
|
setPartOf(partOf) {
|
|
6833
6867
|
return bound().setPartOf(partOf);
|
|
6834
6868
|
},
|
|
6869
|
+
setNetwork(network) {
|
|
6870
|
+
return bound().setNetwork(network);
|
|
6871
|
+
},
|
|
6835
6872
|
removeMember(alias) {
|
|
6836
6873
|
return bound().removeMember(alias);
|
|
6837
6874
|
},
|
|
@@ -7303,10 +7340,13 @@ var ComponentSpecSchema = import_zod5.z.object({
|
|
|
7303
7340
|
/** Other L2 component ids this component collaborates with (facades / standalone blocks). */
|
|
7304
7341
|
dependsOn: import_zod5.z.array(import_zod5.z.string()).default([]),
|
|
7305
7342
|
/**
|
|
7306
|
-
* Portal
|
|
7307
|
-
* TransportSchema). Replaces the retired portalType, which the loader
|
|
7308
|
-
* compatibly for one release (HTTP_API as HTTP). Which process serves
|
|
7309
|
-
* Portal is implementation: there are no listener mounts.
|
|
7343
|
+
* On a Portal (and expected on every Portal): how callers reach its verbs
|
|
7344
|
+
* (see TransportSchema). Replaces the retired portalType, which the loader
|
|
7345
|
+
* reads compatibly for one release (HTTP_API as HTTP). Which process serves
|
|
7346
|
+
* which Portal is implementation: there are no listener mounts. On an
|
|
7347
|
+
* Adapter, optional: the transport it calls its target Portal over —
|
|
7348
|
+
* inferred from the target when absent, and ADAPTER_TRANSPORT_MISMATCH when
|
|
7349
|
+
* stated and different from the target's. On no other block.
|
|
7310
7350
|
*/
|
|
7311
7351
|
transport: TransportSchema.optional(),
|
|
7312
7352
|
/**
|
|
@@ -7971,7 +8011,13 @@ var TypeFieldSchema = import_zod5.z.object({
|
|
|
7971
8011
|
* For foreign keys, the referenced type/table ID (e.g. "billing.Invoice")
|
|
7972
8012
|
* and optionally field (e.g. "billing.Invoice.id").
|
|
7973
8013
|
*/
|
|
7974
|
-
references: import_zod5.z.string().optional()
|
|
8014
|
+
references: import_zod5.z.string().optional(),
|
|
8015
|
+
/**
|
|
8016
|
+
* The field's rename trace: every name it held before, oldest first.
|
|
8017
|
+
* Written only by the rename-field tool, never rewritten; the design export
|
|
8018
|
+
* shows it as `formerly`, so a consumer holding the old name can follow it.
|
|
8019
|
+
*/
|
|
8020
|
+
previousNames: PreviousNamesSchema.optional()
|
|
7975
8021
|
});
|
|
7976
8022
|
var TypeMethodSchema = import_zod5.z.object({
|
|
7977
8023
|
name: import_zod5.z.string(),
|
|
@@ -8121,14 +8167,17 @@ function typeSourceFiles(type) {
|
|
|
8121
8167
|
var DESIGN_VIEW_FIELDS = {
|
|
8122
8168
|
implementation: {
|
|
8123
8169
|
linkage: ["sourcePath", "simPath", "router", "injectedParams", "conformance", "createdAt", "updatedAt"],
|
|
8124
|
-
|
|
8170
|
+
readiness: ["status"],
|
|
8171
|
+
design: ["id", "name", "description", "contract", "technologies", "methods", "detail", "previousIds", "lint", "ext"]
|
|
8125
8172
|
},
|
|
8126
8173
|
implementationMethod: {
|
|
8127
8174
|
linkage: ["sourcePath", "symbol", "exportedVia", "conformance"],
|
|
8175
|
+
readiness: [],
|
|
8128
8176
|
design: ["name", "narrative", "detail", "intent", "calls", "ext"]
|
|
8129
8177
|
},
|
|
8130
8178
|
type: {
|
|
8131
8179
|
linkage: ["sourcePath", "symbol", "createdAt", "updatedAt"],
|
|
8180
|
+
readiness: [],
|
|
8132
8181
|
design: [
|
|
8133
8182
|
"kind",
|
|
8134
8183
|
"id",
|
|
@@ -8154,10 +8203,12 @@ var DESIGN_VIEW_FIELDS = {
|
|
|
8154
8203
|
},
|
|
8155
8204
|
typeMethod: {
|
|
8156
8205
|
linkage: ["sourcePath", "symbol"],
|
|
8206
|
+
readiness: [],
|
|
8157
8207
|
design: ["name", "signature", "params", "returns", "description"]
|
|
8158
8208
|
},
|
|
8159
8209
|
component: {
|
|
8160
8210
|
linkage: ["externalLinks", "createdAt", "updatedAt"],
|
|
8211
|
+
readiness: ["status"],
|
|
8161
8212
|
design: [
|
|
8162
8213
|
"id",
|
|
8163
8214
|
"name",
|
|
@@ -8180,10 +8231,33 @@ var DESIGN_VIEW_FIELDS = {
|
|
|
8180
8231
|
"variant",
|
|
8181
8232
|
"previousIds",
|
|
8182
8233
|
"lint",
|
|
8183
|
-
"ext"
|
|
8184
|
-
|
|
8234
|
+
"ext"
|
|
8235
|
+
]
|
|
8236
|
+
},
|
|
8237
|
+
subsystem: {
|
|
8238
|
+
linkage: ["createdAt", "updatedAt"],
|
|
8239
|
+
readiness: ["status"],
|
|
8240
|
+
design: [
|
|
8241
|
+
"id",
|
|
8242
|
+
"name",
|
|
8243
|
+
"description",
|
|
8244
|
+
"parentSystem",
|
|
8245
|
+
"publicInterfaces",
|
|
8246
|
+
"lifecycle",
|
|
8247
|
+
"profile",
|
|
8248
|
+
"projectPath",
|
|
8249
|
+
"targetLanguage",
|
|
8250
|
+
"trustedLinks",
|
|
8251
|
+
"designDepth",
|
|
8252
|
+
"lint",
|
|
8253
|
+
"ext"
|
|
8185
8254
|
]
|
|
8186
8255
|
},
|
|
8256
|
+
interface: {
|
|
8257
|
+
linkage: ["createdAt", "updatedAt"],
|
|
8258
|
+
readiness: ["status"],
|
|
8259
|
+
design: ["id", "name", "description", "component", "methods", "previousIds", "lint", "ext", "implements"]
|
|
8260
|
+
},
|
|
8187
8261
|
/**
|
|
8188
8262
|
* A retired listener mount as a stored file may still hold it until doctor
|
|
8189
8263
|
* --fix migrates it: its `via` was linkage (the router entry, now the
|
|
@@ -8191,9 +8265,13 @@ var DESIGN_VIEW_FIELDS = {
|
|
|
8191
8265
|
*/
|
|
8192
8266
|
retiredMount: {
|
|
8193
8267
|
linkage: ["via"],
|
|
8268
|
+
readiness: [],
|
|
8194
8269
|
design: ["portal", "prefixes"]
|
|
8195
8270
|
}
|
|
8196
8271
|
};
|
|
8272
|
+
function outOfApproval(kind) {
|
|
8273
|
+
return [...DESIGN_VIEW_FIELDS[kind].linkage, ...DESIGN_VIEW_FIELDS[kind].readiness];
|
|
8274
|
+
}
|
|
8197
8275
|
function withoutFields(value, fields) {
|
|
8198
8276
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
8199
8277
|
const out = {};
|
|
@@ -8206,20 +8284,26 @@ function eachOf(items, project3) {
|
|
|
8206
8284
|
return Array.isArray(items) ? items.map(project3) : items;
|
|
8207
8285
|
}
|
|
8208
8286
|
function implementationDesignView(impl) {
|
|
8209
|
-
const view3 = withoutFields(impl,
|
|
8287
|
+
const view3 = withoutFields(impl, outOfApproval("implementation"));
|
|
8210
8288
|
if ("methods" in view3) view3.methods = eachOf(view3.methods, (m) => withoutFields(m, DESIGN_VIEW_FIELDS.implementationMethod.linkage));
|
|
8211
8289
|
return view3;
|
|
8212
8290
|
}
|
|
8213
8291
|
function typeDesignView(type) {
|
|
8214
|
-
const view3 = withoutFields(type,
|
|
8292
|
+
const view3 = withoutFields(type, outOfApproval("type"));
|
|
8215
8293
|
if ("methods" in view3) view3.methods = eachOf(view3.methods, (m) => withoutFields(m, DESIGN_VIEW_FIELDS.typeMethod.linkage));
|
|
8216
8294
|
return view3;
|
|
8217
8295
|
}
|
|
8218
8296
|
function componentDesignView(component) {
|
|
8219
|
-
const view3 = withoutFields(component,
|
|
8297
|
+
const view3 = withoutFields(component, outOfApproval("component"));
|
|
8220
8298
|
if ("mounts" in view3) view3.mounts = eachOf(view3.mounts, (m) => withoutFields(m, DESIGN_VIEW_FIELDS.retiredMount.linkage));
|
|
8221
8299
|
return view3;
|
|
8222
8300
|
}
|
|
8301
|
+
function subsystemDesignView(subsystem) {
|
|
8302
|
+
return withoutFields(subsystem, outOfApproval("subsystem"));
|
|
8303
|
+
}
|
|
8304
|
+
function interfaceDesignView(contract) {
|
|
8305
|
+
return withoutFields(contract, outOfApproval("interface"));
|
|
8306
|
+
}
|
|
8223
8307
|
function componentEntryFor(component, method) {
|
|
8224
8308
|
if (component.componentType !== "Portal") return void 0;
|
|
8225
8309
|
const own2 = component.invokedBy;
|
|
@@ -8421,6 +8505,14 @@ var SurfaceContractEntrySchema = import_zod5.z.object({
|
|
|
8421
8505
|
/** The entry's export role, call (the default) or implement. */
|
|
8422
8506
|
role: import_zod5.z.string().optional()
|
|
8423
8507
|
});
|
|
8508
|
+
var SurfaceUnresolvedExportSchema = import_zod5.z.object({
|
|
8509
|
+
/** The public name the entry declares. */
|
|
8510
|
+
id: import_zod5.z.string(),
|
|
8511
|
+
/** Where it re-exports from: the `from` the entry names. */
|
|
8512
|
+
source: import_zod5.z.string(),
|
|
8513
|
+
/** The name the source's rename trace carries the item to, when it records one. */
|
|
8514
|
+
renamedTo: import_zod5.z.string().optional()
|
|
8515
|
+
});
|
|
8424
8516
|
var SurfaceSnapshotSchema = import_zod5.z.object({
|
|
8425
8517
|
/** Producing project/system name — the snapshot's resolution identity. */
|
|
8426
8518
|
projectName: import_zod5.z.string(),
|
|
@@ -8448,7 +8540,14 @@ var SurfaceSnapshotSchema = import_zod5.z.object({
|
|
|
8448
8540
|
* language judges a native-ABI library call against. Absent when the
|
|
8449
8541
|
* producer declares none, or on snapshots written before it was carried.
|
|
8450
8542
|
*/
|
|
8451
|
-
targetLanguage: import_zod5.z.string().optional()
|
|
8543
|
+
targetLanguage: import_zod5.z.string().optional(),
|
|
8544
|
+
/**
|
|
8545
|
+
* The public names the producer's L0 declares that no longer resolve (a
|
|
8546
|
+
* named re-export whose source dropped or renamed the item), with the
|
|
8547
|
+
* source's rename trace when it has one. Provenance, outside every digest;
|
|
8548
|
+
* absent when every entry resolves.
|
|
8549
|
+
*/
|
|
8550
|
+
unresolvedExports: import_zod5.z.array(SurfaceUnresolvedExportSchema).optional()
|
|
8452
8551
|
});
|
|
8453
8552
|
var ExternalLockEntrySchema = import_zod5.z.object({
|
|
8454
8553
|
project: import_zod5.z.string(),
|
|
@@ -8483,7 +8582,7 @@ var ParentExcerptSchema = import_zod5.z.object({
|
|
|
8483
8582
|
function relationHealth(status) {
|
|
8484
8583
|
if (status.uses.some((u) => u.state === "changed" || u.state === "removed" || u.state === "renamed")) return "incompatible";
|
|
8485
8584
|
if (!status.reachable || status.outOfReach || status.uses.some((u) => u.state === "unavailable" || u.state === "unlocked")) return "unavailable";
|
|
8486
|
-
if (status.drifted) return "drifted";
|
|
8585
|
+
if (status.drifted || (status.staleFacts?.length ?? 0) > 0) return "drifted";
|
|
8487
8586
|
return "ok";
|
|
8488
8587
|
}
|
|
8489
8588
|
var NamedOpenApiSpecSchema = import_zod5.z.object({
|
|
@@ -8524,7 +8623,8 @@ function sortKeys(v) {
|
|
|
8524
8623
|
|
|
8525
8624
|
// src/core/statehash.ts
|
|
8526
8625
|
var CONTENT_ALGORITHM = "sha256";
|
|
8527
|
-
var DESIGN_ALGORITHM = "sha256-design";
|
|
8626
|
+
var DESIGN_ALGORITHM = "sha256-design-2";
|
|
8627
|
+
var EARLIER_DESIGN_ALGORITHM = "sha256-design";
|
|
8528
8628
|
function inIdentityOrder(specs) {
|
|
8529
8629
|
return specs.map((spec) => ({ spec, content: canonicalize(spec) })).sort((a, b) => compareOrdinal(a.spec.id, b.spec.id) || compareOrdinal(a.content, b.content)).map((entry) => entry.spec);
|
|
8530
8630
|
}
|
|
@@ -8533,9 +8633,9 @@ function digestTree(keep, reading = {}) {
|
|
|
8533
8633
|
const same = (spec) => spec;
|
|
8534
8634
|
const tree = {
|
|
8535
8635
|
system: loadSystemSpec(),
|
|
8536
|
-
subsystems: kind(loadSubsystemSpecs()),
|
|
8636
|
+
subsystems: kind(loadSubsystemSpecs().map(reading.subsystem ?? same)),
|
|
8537
8637
|
components: kind(loadComponentSpecs().map(reading.component ?? same)),
|
|
8538
|
-
interfaces: kind(loadInterfaceSpecs()),
|
|
8638
|
+
interfaces: kind(loadInterfaceSpecs().map(reading.interface ?? same)),
|
|
8539
8639
|
implementations: kind(loadImplementationSpecs().map(reading.implementation ?? same)),
|
|
8540
8640
|
types: kind(loadTypeSpecs().map(reading.type ?? same))
|
|
8541
8641
|
};
|
|
@@ -8567,9 +8667,31 @@ function computeOwnDesignStateId() {
|
|
|
8567
8667
|
implementation: implementationDesignView,
|
|
8568
8668
|
component: componentDesignView,
|
|
8569
8669
|
type: typeDesignView,
|
|
8670
|
+
subsystem: subsystemDesignView,
|
|
8671
|
+
interface: interfaceDesignView,
|
|
8570
8672
|
algorithm: DESIGN_ALGORITHM
|
|
8571
8673
|
});
|
|
8572
8674
|
}
|
|
8675
|
+
function approvedStatusKey(kind, id) {
|
|
8676
|
+
return `${kind}:${id}`;
|
|
8677
|
+
}
|
|
8678
|
+
function computeOwnDesignStateIdAsRecorded(approvedStatus) {
|
|
8679
|
+
const withStatus = (kind, view3) => (spec) => {
|
|
8680
|
+
const status = spec.status;
|
|
8681
|
+
if (status === void 0) return view3(spec);
|
|
8682
|
+
const id = spec.id;
|
|
8683
|
+
const approved = typeof id === "string" ? approvedStatus.get(approvedStatusKey(kind, id)) : void 0;
|
|
8684
|
+
return { ...view3(spec), status: approved ?? status };
|
|
8685
|
+
};
|
|
8686
|
+
return digestTree(ownedByRoot(), {
|
|
8687
|
+
implementation: withStatus("implementation", implementationDesignView),
|
|
8688
|
+
component: withStatus("component", componentDesignView),
|
|
8689
|
+
type: withStatus("type", typeDesignView),
|
|
8690
|
+
subsystem: withStatus("subsystem", subsystemDesignView),
|
|
8691
|
+
interface: withStatus("interface", interfaceDesignView),
|
|
8692
|
+
algorithm: EARLIER_DESIGN_ALGORITHM
|
|
8693
|
+
});
|
|
8694
|
+
}
|
|
8573
8695
|
function isInside(dir, file) {
|
|
8574
8696
|
const rel2 = path7.relative(dir, file);
|
|
8575
8697
|
return rel2 !== "" && !rel2.startsWith("..") && !path7.isAbsolute(rel2);
|
|
@@ -9815,6 +9937,54 @@ var DIALECTS = /* @__PURE__ */ new Map([
|
|
|
9815
9937
|
function typeDialectFor(language) {
|
|
9816
9938
|
return DIALECTS.get(language.toLowerCase()) ?? null;
|
|
9817
9939
|
}
|
|
9940
|
+
var MAPPING_ONLY = (language) => `(mapping only: wairon's code analyzer does not read ${language} annotations yet, so code conformance does not compare these types \u2014 follow the table by hand)`;
|
|
9941
|
+
var MAPPING_ONLY_TABLES = /* @__PURE__ */ new Map([
|
|
9942
|
+
["rust", [
|
|
9943
|
+
"string \u2192 String (&str for a borrowed parameter)",
|
|
9944
|
+
"int \u2192 i64 (a narrower iN/uN where the contract says so in its description)",
|
|
9945
|
+
"float \u2192 f64",
|
|
9946
|
+
"bool \u2192 bool",
|
|
9947
|
+
"bytes \u2192 Vec<u8> (&[u8] for a borrowed parameter)",
|
|
9948
|
+
"date \u2192 chrono::NaiveDate, datetime \u2192 chrono::DateTime<Utc>, duration \u2192 std::time::Duration",
|
|
9949
|
+
"any \u2192 serde_json::Value",
|
|
9950
|
+
"list<T> \u2192 Vec<T>",
|
|
9951
|
+
"set<T> \u2192 HashSet<T> (BTreeSet<T> where order matters)",
|
|
9952
|
+
"map<K, V> \u2192 HashMap<K, V> (BTreeMap<K, V> where order matters)",
|
|
9953
|
+
"T? \u2192 Option<T>",
|
|
9954
|
+
"A | B \u2192 an enum with one variant per named type",
|
|
9955
|
+
"async T \u2192 async fn returning T",
|
|
9956
|
+
"result<T, E> \u2192 Result<T, E> (result<void, E> \u2192 Result<(), E>)",
|
|
9957
|
+
"an enum E \u2192 enum E { A, B } (unit variants)",
|
|
9958
|
+
"a named scalar E holding P \u2192 a newtype struct E(P)",
|
|
9959
|
+
"a contract (interface) \u2192 a trait; a type method \u2192 an impl method on the struct"
|
|
9960
|
+
]],
|
|
9961
|
+
["python", [
|
|
9962
|
+
"string \u2192 str",
|
|
9963
|
+
"int \u2192 int",
|
|
9964
|
+
"float \u2192 float",
|
|
9965
|
+
"bool \u2192 bool",
|
|
9966
|
+
"bytes \u2192 bytes",
|
|
9967
|
+
"date \u2192 datetime.date, datetime \u2192 datetime.datetime, duration \u2192 datetime.timedelta",
|
|
9968
|
+
"any \u2192 typing.Any",
|
|
9969
|
+
"list<T> \u2192 list[T]",
|
|
9970
|
+
"set<T> \u2192 set[T]",
|
|
9971
|
+
"map<K, V> \u2192 dict[K, V]",
|
|
9972
|
+
"T? \u2192 T | None (Optional[T])",
|
|
9973
|
+
"A | B \u2192 A | B",
|
|
9974
|
+
"async T \u2192 async def returning T",
|
|
9975
|
+
"result<T, E> \u2192 T, raising E on failure (result<void, E> \u2192 None)",
|
|
9976
|
+
"an enum E \u2192 class E(enum.Enum)",
|
|
9977
|
+
'a named scalar E holding P \u2192 E = NewType("E", P)',
|
|
9978
|
+
"a contract (interface) \u2192 a typing.Protocol; an entity or value-object \u2192 a @dataclass"
|
|
9979
|
+
]]
|
|
9980
|
+
]);
|
|
9981
|
+
function typeMappingFor(language) {
|
|
9982
|
+
const dialect = typeDialectFor(language);
|
|
9983
|
+
if (dialect) return dialect.mappingLines();
|
|
9984
|
+
const key2 = language.toLowerCase();
|
|
9985
|
+
const table = MAPPING_ONLY_TABLES.get(key2);
|
|
9986
|
+
return table ? [...table, MAPPING_ONLY(language)] : null;
|
|
9987
|
+
}
|
|
9818
9988
|
|
|
9819
9989
|
// src/models/step-graph.ts
|
|
9820
9990
|
function stepGraph(method) {
|
|
@@ -10285,6 +10455,45 @@ function contentDigest(snapshot) {
|
|
|
10285
10455
|
};
|
|
10286
10456
|
return sha256(canonicalize(contract));
|
|
10287
10457
|
}
|
|
10458
|
+
function factValue(value) {
|
|
10459
|
+
return value === void 0 || value === null ? "" : canonicalize(value);
|
|
10460
|
+
}
|
|
10461
|
+
function carriedFactChanges(pinned, live) {
|
|
10462
|
+
const changes = [];
|
|
10463
|
+
const differs = (label7, a, b) => {
|
|
10464
|
+
if (factValue(a) !== factValue(b)) changes.push(label7);
|
|
10465
|
+
};
|
|
10466
|
+
differs("the producer's id", pinned.projectId, live.projectId);
|
|
10467
|
+
differs("targetLanguage", pinned.targetLanguage, live.targetLanguage);
|
|
10468
|
+
const liveEntries = new Map(live.interfaces.map((e) => [e.id, e]));
|
|
10469
|
+
for (const entry of pinned.interfaces) {
|
|
10470
|
+
const now = liveEntries.get(entry.id);
|
|
10471
|
+
if (!now) continue;
|
|
10472
|
+
differs(`export kind of ${entry.id}`, entry.type, now.type);
|
|
10473
|
+
differs(`transport of ${entry.id}`, entry.transport, now.transport);
|
|
10474
|
+
differs(`abi of ${entry.id}`, entry.abi, now.abi);
|
|
10475
|
+
differs(`role of ${entry.id}`, entry.role, now.role);
|
|
10476
|
+
differs(`stereotype of ${entry.id}`, entry.componentType, now.componentType);
|
|
10477
|
+
differs(`basePath of ${entry.id}`, entry.basePath, now.basePath);
|
|
10478
|
+
differs(`auth of ${entry.id}`, entry.auth?.scheme, now.auth?.scheme);
|
|
10479
|
+
differs(`rename trace of ${entry.id}`, entry.formerly, now.formerly);
|
|
10480
|
+
const liveMethods = new Map(now.methods.map((m) => [m.name, m]));
|
|
10481
|
+
for (const method of entry.methods) {
|
|
10482
|
+
const nowMethod = liveMethods.get(method.name);
|
|
10483
|
+
if (!nowMethod) continue;
|
|
10484
|
+
differs(`effect of ${entry.id}.${method.name}`, method.effect, nowMethod.effect);
|
|
10485
|
+
differs(`rename trace of ${entry.id}.${method.name}`, method.formerly, nowMethod.formerly);
|
|
10486
|
+
}
|
|
10487
|
+
}
|
|
10488
|
+
const liveTypes = new Map(live.types.map((t) => [t.id, t]));
|
|
10489
|
+
for (const def of pinned.types) {
|
|
10490
|
+
const now = liveTypes.get(def.id);
|
|
10491
|
+
if (now) differs(`rename trace of ${def.id}`, def.formerly, now.formerly);
|
|
10492
|
+
}
|
|
10493
|
+
const unresolved = (snapshot) => [...snapshot.unresolvedExports ?? []].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
10494
|
+
differs("the unresolved re-exports", unresolved(pinned), unresolved(live));
|
|
10495
|
+
return changes;
|
|
10496
|
+
}
|
|
10288
10497
|
function memberDigest(snapshot, publicName, member) {
|
|
10289
10498
|
if (member === "type") {
|
|
10290
10499
|
const exported = snapshot.exportedTypes?.find((t) => t.id === publicName);
|
|
@@ -10318,6 +10527,38 @@ function exportTargetKey(e) {
|
|
|
10318
10527
|
|
|
10319
10528
|
// src/models/project-family.ts
|
|
10320
10529
|
var path11 = __toESM(require("path"));
|
|
10530
|
+
function directoryKey(dir) {
|
|
10531
|
+
const resolved = path11.resolve(dir);
|
|
10532
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
10533
|
+
}
|
|
10534
|
+
function relationOf(consumer, producer) {
|
|
10535
|
+
if (consumer.parent === producer.namespace) return "parent";
|
|
10536
|
+
if (producer.parent === consumer.namespace) return "member";
|
|
10537
|
+
if (consumer.parent !== void 0 && consumer.parent === producer.parent) return "sibling";
|
|
10538
|
+
return "family";
|
|
10539
|
+
}
|
|
10540
|
+
function familyExternal(alias, project3, consumer, producer) {
|
|
10541
|
+
return { alias, project: project3, sourceKind: "family", relation: relationOf(consumer, producer), producer: producer.namespace, directory: producer.directory, audience: "project", role: "external" };
|
|
10542
|
+
}
|
|
10543
|
+
function bindDeclaredExternal(declared, consumer, nodes) {
|
|
10544
|
+
const unresolved = (problem) => ({ alias: declared.alias, project: declared.project, sourceKind: "unresolved", audience: "instance", problem, role: "external" });
|
|
10545
|
+
if (declared.problem) return unresolved(declared.problem);
|
|
10546
|
+
if (declared.sourceHosted !== void 0) return { alias: declared.alias, project: declared.project, sourceKind: "hosted", hosted: declared.sourceHosted, audience: "instance", role: "external" };
|
|
10547
|
+
if (declared.sourceGit !== void 0) return { alias: declared.alias, project: declared.project, sourceKind: "git", audience: "instance", role: "external" };
|
|
10548
|
+
if (declared.sourcePath !== void 0) {
|
|
10549
|
+
const directory = path11.resolve(consumer.directory, declared.sourcePath);
|
|
10550
|
+
const found = nodes.find((n) => directoryKey(n.directory) === directoryKey(directory));
|
|
10551
|
+
if (!found) return { alias: declared.alias, project: declared.project, sourceKind: "path", directory, audience: "instance", role: "external" };
|
|
10552
|
+
if (found.id !== declared.project) {
|
|
10553
|
+
return unresolved(`the family project at source.path "${declared.sourcePath}" answers to ${found.id === void 0 ? "no id" : `"${found.id}"`}, not "${declared.project}"`);
|
|
10554
|
+
}
|
|
10555
|
+
return familyExternal(declared.alias, declared.project, consumer, found);
|
|
10556
|
+
}
|
|
10557
|
+
const holders = nodes.filter((n) => n.id === declared.project && n !== consumer);
|
|
10558
|
+
if (holders.length === 0) return unresolved(`no project of the family answers to "${declared.project}", and no source.path says where to find it`);
|
|
10559
|
+
if (holders.length > 1) return unresolved(`${holders.length} projects of the family answer to "${declared.project}" (${holders.map((n) => `"${n.namespace || "(the bound root)"}"`).join(", ")}), so the declaration could mean either`);
|
|
10560
|
+
return familyExternal(declared.alias, declared.project, consumer, holders[0]);
|
|
10561
|
+
}
|
|
10321
10562
|
function walkMemberPath(byKey3, start, segments) {
|
|
10322
10563
|
let node = start;
|
|
10323
10564
|
for (const segment of segments.slice(0, -1)) {
|
|
@@ -10541,7 +10782,9 @@ var DesignFieldSchema = import_zod9.z.object({
|
|
|
10541
10782
|
optional: import_zod9.z.boolean(),
|
|
10542
10783
|
description: import_zod9.z.string().optional(),
|
|
10543
10784
|
key: import_zod9.z.string().optional(),
|
|
10544
|
-
references: import_zod9.z.string().optional()
|
|
10785
|
+
references: import_zod9.z.string().optional(),
|
|
10786
|
+
/** The field's rename trace, oldest first — present only when it was renamed. */
|
|
10787
|
+
formerly: import_zod9.z.array(import_zod9.z.string()).optional()
|
|
10545
10788
|
}).passthrough();
|
|
10546
10789
|
var DeclaredInvocationSchema2 = DeclaredInvocationSchema.passthrough();
|
|
10547
10790
|
var DesignMethodSchema = import_zod9.z.object({
|
|
@@ -16902,7 +17145,10 @@ function typeSection(t, keys) {
|
|
|
16902
17145
|
optional: f.optional === true,
|
|
16903
17146
|
...f.description !== void 0 ? { description: f.description } : {},
|
|
16904
17147
|
...f.key !== void 0 ? { key: f.key } : {},
|
|
16905
|
-
...f.references !== void 0 ? { references: keys.typeFieldRef(f.references, owner) } : {}
|
|
17148
|
+
...f.references !== void 0 ? { references: keys.typeFieldRef(f.references, owner) } : {},
|
|
17149
|
+
// The field's rename trace, only when it was renamed: an export of a
|
|
17150
|
+
// never-renamed field reads exactly as before.
|
|
17151
|
+
...f.previousNames && f.previousNames.length > 0 ? { formerly: [...f.previousNames] } : {}
|
|
16906
17152
|
})),
|
|
16907
17153
|
methods: t.methods.map((m) => ({
|
|
16908
17154
|
key: `${key2}.${m.name}`,
|
|
@@ -17161,6 +17407,7 @@ function projectOwnSurface(maxAudience) {
|
|
|
17161
17407
|
};
|
|
17162
17408
|
const projectId = boundProjectId();
|
|
17163
17409
|
const targetLanguage = system.targetLanguage?.trim().toLowerCase();
|
|
17410
|
+
const unresolvedExports = table.problems.filter((p) => p.kind === "invalid" && p.publicName !== void 0 && p.source !== void 0 && !table.entries.some((e) => e.publicName === p.publicName)).map((p) => ({ id: p.publicName, source: p.source, ...p.renamedTo ? { renamedTo: p.renamedTo } : {} }));
|
|
17164
17411
|
return canonicalReferences(SurfaceSnapshotSchema.parse({
|
|
17165
17412
|
projectName: system.name,
|
|
17166
17413
|
...projectId ? { projectId } : {},
|
|
@@ -17170,7 +17417,8 @@ function projectOwnSurface(maxAudience) {
|
|
|
17170
17417
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17171
17418
|
interfaces: entries,
|
|
17172
17419
|
types: closure,
|
|
17173
|
-
...exportedTypes.length ? { exportedTypes: exportedTypes.map((e) => ({ id: e.publicName, type: closureIdOf(e), audience: e.audience ?? "instance" })) } : {}
|
|
17420
|
+
...exportedTypes.length ? { exportedTypes: exportedTypes.map((e) => ({ id: e.publicName, type: closureIdOf(e), audience: e.audience ?? "instance" })) } : {},
|
|
17421
|
+
...unresolvedExports.length ? { unresolvedExports } : {}
|
|
17174
17422
|
}));
|
|
17175
17423
|
}
|
|
17176
17424
|
function canonicalReferences(snapshot) {
|
|
@@ -17480,7 +17728,7 @@ function pinBinding(binding, lock) {
|
|
|
17480
17728
|
}
|
|
17481
17729
|
const digest3 = contentDigest(snapshot);
|
|
17482
17730
|
const pinned = externalsRepository.readSnapshot(external.alias);
|
|
17483
|
-
const snapshotChanged = !pinned || contentDigest(pinned) !== digest3;
|
|
17731
|
+
const snapshotChanged = !pinned || contentDigest(pinned) !== digest3 || pinnedContentKey(pinned) !== pinnedContentKey(snapshot);
|
|
17484
17732
|
if (snapshotChanged) externalsRepository.saveSnapshot(external.alias, snapshot);
|
|
17485
17733
|
const { used, missing } = usedDigests(binding.usage, snapshot);
|
|
17486
17734
|
const entry = {
|
|
@@ -17623,7 +17871,15 @@ function sameSignatureAfterRename(live, from, to, digest3) {
|
|
|
17623
17871
|
function goneUse(live, publicName, member, digest3) {
|
|
17624
17872
|
const to = renamedIn(live, publicName, member);
|
|
17625
17873
|
const at = { publicName, ...member !== void 0 ? { member } : {} };
|
|
17626
|
-
if (!to)
|
|
17874
|
+
if (!to) {
|
|
17875
|
+
const broken = (live.unresolvedExports ?? []).find((u) => u.id === publicName);
|
|
17876
|
+
if (!broken) return { ...at, state: "removed" };
|
|
17877
|
+
return {
|
|
17878
|
+
...at,
|
|
17879
|
+
state: "removed",
|
|
17880
|
+
detail: `the producer re-exports it from "${broken.source}", which ${broken.renamedTo ? `renamed it to "${broken.renamedTo}"` : "no longer exports it"}, so the producer's re-export no longer resolves \u2014 ${broken.renamedTo ? `the producer must re-export "${broken.renamedTo}", or depend on "${broken.source}" directly` : "the producer must fix its re-export"}`
|
|
17881
|
+
};
|
|
17882
|
+
}
|
|
17627
17883
|
const renamedTo = renamedName(to);
|
|
17628
17884
|
const signature = member === void 0 || digest3 === void 0 ? "" : sameSignatureAfterRename(live, { publicName, member }, to, digest3) ? " (its signature is unchanged)" : " (and its signature changed)";
|
|
17629
17885
|
return { ...at, state: "renamed", renamedTo, detail: `renamed to "${renamedTo}"${signature} \u2014 follow the rename` };
|
|
@@ -17642,13 +17898,19 @@ function compareUses(entry, usage, live) {
|
|
|
17642
17898
|
uses.push(now === null ? goneUse(live, publicName, member, digest3) : { publicName, member, state: now === digest3 ? "unchanged" : "changed" });
|
|
17643
17899
|
}
|
|
17644
17900
|
}
|
|
17901
|
+
const unlocked = (publicName, member) => {
|
|
17902
|
+
const present = member === void 0 ? liveHas(publicName) : memberDigest(live, publicName, member) !== null;
|
|
17903
|
+
if (!present) return goneUse(live, publicName, member, void 0);
|
|
17904
|
+
return { publicName, ...member !== void 0 ? { member } : {}, state: "unlocked", detail: entry === void 0 ? "used, and present in the live producer, but never pinned \u2014 pin it (`wairon externals pin`) to record what it is judged against" : "used now, but not in the lock \u2014 re-pin" };
|
|
17905
|
+
};
|
|
17645
17906
|
for (const use of usage?.used ?? []) {
|
|
17646
17907
|
if (!(use.publicName in locked)) {
|
|
17647
|
-
|
|
17908
|
+
const gone = use.members.map((m) => unlocked(use.publicName, m)).filter((u) => u.state !== "unlocked");
|
|
17909
|
+
uses.push(...gone.length ? gone : [unlocked(use.publicName)]);
|
|
17648
17910
|
continue;
|
|
17649
17911
|
}
|
|
17650
17912
|
for (const member of use.members) {
|
|
17651
|
-
if (!(member in locked[use.publicName])) uses.push(
|
|
17913
|
+
if (!(member in locked[use.publicName])) uses.push(unlocked(use.publicName, member));
|
|
17652
17914
|
}
|
|
17653
17915
|
}
|
|
17654
17916
|
for (const ref of usage?.unexported ?? []) {
|
|
@@ -17700,11 +17962,14 @@ function externalStatus(binding, lock) {
|
|
|
17700
17962
|
}
|
|
17701
17963
|
const drifted = entry !== void 0 ? contentDigest(live) !== entry.digest : void 0;
|
|
17702
17964
|
const uses = compareUses(entry, binding.usage, live);
|
|
17965
|
+
const snapshot = entry !== void 0 ? externalsRepository.readSnapshot(external.alias) : null;
|
|
17966
|
+
const staleFacts = snapshot ? carriedFactChanges(snapshot, live) : [];
|
|
17703
17967
|
return {
|
|
17704
17968
|
...base,
|
|
17705
17969
|
reachable: true,
|
|
17706
17970
|
stale: uses.some((u) => u.state === "changed" || u.state === "removed" || u.state === "renamed"),
|
|
17707
17971
|
...drifted !== void 0 ? { drifted } : {},
|
|
17972
|
+
...staleFacts.length ? { staleFacts } : {},
|
|
17708
17973
|
uses
|
|
17709
17974
|
};
|
|
17710
17975
|
}
|
|
@@ -17722,7 +17987,7 @@ function getExternalsStatus(offline) {
|
|
|
17722
17987
|
function listExternals() {
|
|
17723
17988
|
const bindings = resolveDeclared();
|
|
17724
17989
|
const lock = externalsRepository.readLock();
|
|
17725
|
-
|
|
17990
|
+
const declared = bindings.map(({ external }) => {
|
|
17726
17991
|
const entry = lock?.externals[external.alias];
|
|
17727
17992
|
return {
|
|
17728
17993
|
alias: external.alias,
|
|
@@ -17735,6 +18000,20 @@ function listExternals() {
|
|
|
17735
18000
|
...external.problem ? { problem: external.problem } : {}
|
|
17736
18001
|
};
|
|
17737
18002
|
});
|
|
18003
|
+
const aliases = new Set(bindings.map((b) => b.external.alias));
|
|
18004
|
+
const orphans = Object.entries(lock?.externals ?? {}).filter(([alias]) => !aliases.has(alias)).map(([alias, entry]) => ({
|
|
18005
|
+
alias,
|
|
18006
|
+
project: entry.project,
|
|
18007
|
+
sourceKind: "unresolved",
|
|
18008
|
+
audience: "instance",
|
|
18009
|
+
lock: entry,
|
|
18010
|
+
problem: `pinned but no longer declared \u2014 \`wairon externals remove ${alias}\` removes the pin`
|
|
18011
|
+
}));
|
|
18012
|
+
return [...declared, ...orphans];
|
|
18013
|
+
}
|
|
18014
|
+
function pinnedContentKey(snapshot) {
|
|
18015
|
+
const { stateId: _s, generatedAt: _g, origin: _o, ...content } = SurfaceSnapshotSchema.parse(JSON.parse(JSON.stringify(snapshot)));
|
|
18016
|
+
return canonicalize({ ...content, interfaces: content.interfaces.map((e) => ({ ...e, component: lastSegment2(e.component) })) });
|
|
17738
18017
|
}
|
|
17739
18018
|
function surfaceContentKey(snapshot) {
|
|
17740
18019
|
const { stateId, generatedAt, origin, ...content } = snapshot;
|
|
@@ -17748,7 +18027,9 @@ function audienceRank2(audience) {
|
|
|
17748
18027
|
const idx = levels.indexOf(audience ?? "instance");
|
|
17749
18028
|
return idx === -1 ? levels.indexOf("instance") : idx;
|
|
17750
18029
|
}
|
|
18030
|
+
var AUDIENCE_RANKING = SURFACE_AUDIENCES.join(" < ");
|
|
17751
18031
|
var LOCATION_FORMS = "`../sibling`, `hosted:<id>`, `<git url>` or `<git url>#<commit>`";
|
|
18032
|
+
var NOT_A_LOCATION = /[\s{}[\]"'`<>|*?]/;
|
|
17752
18033
|
function refused(alias, why2) {
|
|
17753
18034
|
return { alias, written: false, declaration: null, refusal: `The external "${alias}" was not declared: ${why2}.` };
|
|
17754
18035
|
}
|
|
@@ -17756,11 +18037,14 @@ function requestProblem(request, config, source, sourceProblem) {
|
|
|
17756
18037
|
const { alias } = request;
|
|
17757
18038
|
if (!EXTERNAL_ALIAS_RE.test(alias)) return `an alias is [a-z0-9-_]+, and "${alias}" is not \u2014 choose one specs can write as \`${alias.toLowerCase().replace(/[^a-z0-9_-]/g, "-")}::name\``;
|
|
17758
18039
|
if (config?.members?.[alias] !== void 0) return `"${alias}" is already declared under \`members\` \u2014 one alias names one project, so choose another alias`;
|
|
17759
|
-
if (config?.externals?.[alias] !== void 0) return `"${alias}" is already declared under \`externals\` \u2014 choose another alias, or
|
|
18040
|
+
if (config?.externals?.[alias] !== void 0) return `"${alias}" is already declared under \`externals\` \u2014 choose another alias, change its imports with \`wairon externals use ${alias} --add <names>\` (sdd_update_external), or remove it first with \`wairon externals remove ${alias}\``;
|
|
18041
|
+
if (request.source !== void 0 && NOT_A_LOCATION.test(request.source.trim())) {
|
|
18042
|
+
return `the source "${request.source}" is not a location (accepted: ${LOCATION_FORMS})`;
|
|
18043
|
+
}
|
|
17760
18044
|
if (sourceProblem) return `${sourceProblem.replace(/^its /, "the ")} (accepted: ${LOCATION_FORMS})`;
|
|
17761
18045
|
const git2 = source?.git !== void 0;
|
|
17762
18046
|
if (!git2 && (request.ref !== void 0 || request.dir !== void 0)) {
|
|
17763
|
-
return
|
|
18047
|
+
return "`ref` and `dir` apply to a git source only \u2014 write the source as `<git url>` or `<git url>#<commit>`";
|
|
17764
18048
|
}
|
|
17765
18049
|
if (git2 && request.ref !== void 0 && source?.ref !== void 0 && request.ref !== source.ref) {
|
|
17766
18050
|
return `the source fixes the ref at "${source.ref}" (its \`#<commit>\`), but \`ref\` says "${request.ref}" \u2014 give one of them`;
|
|
@@ -17807,13 +18091,28 @@ function readProducer(binding) {
|
|
|
17807
18091
|
const config = loadProjectConfig();
|
|
17808
18092
|
if (!config) return { why: `${directory} holds no wairon project` };
|
|
17809
18093
|
const floor = audienceRank2(external.audience);
|
|
17810
|
-
const
|
|
17811
|
-
|
|
18094
|
+
const entries = resolveProjectExports().entries;
|
|
18095
|
+
const exported = entries.filter((e) => audienceRank2(e.audience ?? "instance") >= floor).map((e) => e.publicName);
|
|
18096
|
+
const audiences = /* @__PURE__ */ new Map();
|
|
18097
|
+
for (const e of entries) {
|
|
18098
|
+
const audience = e.audience ?? "instance";
|
|
18099
|
+
const held = audiences.get(e.publicName);
|
|
18100
|
+
if (held === void 0 || audienceRank2(audience) > audienceRank2(held)) audiences.set(e.publicName, audience);
|
|
18101
|
+
}
|
|
18102
|
+
return { read: { id: effectiveProjectId(config), exported: [...new Set(exported)], audiences, audience: external.audience } };
|
|
17812
18103
|
});
|
|
17813
18104
|
} catch (e) {
|
|
17814
18105
|
return { why: e instanceof Error ? e.message : String(e) };
|
|
17815
18106
|
}
|
|
17816
18107
|
}
|
|
18108
|
+
function unexportedReason(name, read2) {
|
|
18109
|
+
const narrower = read2.audiences.get(name);
|
|
18110
|
+
if (narrower !== void 0) {
|
|
18111
|
+
const who = read2.audience === "project" ? "a project of its family" : "a path, git or hosted consumer outside its family";
|
|
18112
|
+
return `the producer exports "${name}" to \`${narrower}\` only, and this project is outside it \u2014 it reads the producer at \`${read2.audience}\` (${who}); audiences, narrowest first: ${AUDIENCE_RANKING} \u2014 widen the export's audience in the producer's L0, or consume it from inside its family`;
|
|
18113
|
+
}
|
|
18114
|
+
return `the producer does not export "${name}" to this project${read2.exported.length ? ` (closest: ${closest(name, read2.exported).map((n) => `"${n}"`).join(", ")})` : " (it exports nothing to this project)"}`;
|
|
18115
|
+
}
|
|
17817
18116
|
function contradiction(request, binding, read2) {
|
|
17818
18117
|
const declared = request.project ?? request.alias;
|
|
17819
18118
|
const problem = binding?.external.problem;
|
|
@@ -17825,10 +18124,7 @@ function contradiction(request, binding, read2) {
|
|
|
17825
18124
|
return `the producer there answers to "${read2.id}", not "${declared}" \u2014 declare \`project: ${read2.id}\`, or use "${read2.id}" as the alias`;
|
|
17826
18125
|
}
|
|
17827
18126
|
const missing = (request.use ?? []).filter((u) => u !== "*" && !read2.exported.includes(u));
|
|
17828
|
-
|
|
17829
|
-
return missing.map((u) => `the producer does not export "${u}" to this project${read2.exported.length ? ` (closest: ${closest(u, read2.exported).map((n) => `"${n}"`).join(", ")})` : " (it exports nothing to this project)"}`).join("; ");
|
|
17830
|
-
}
|
|
17831
|
-
return null;
|
|
18127
|
+
return missing.length > 0 ? missing.map((u) => unexportedReason(u, read2)).join("; ") : null;
|
|
17832
18128
|
}
|
|
17833
18129
|
function declare(request) {
|
|
17834
18130
|
const { alias } = request;
|
|
@@ -17838,23 +18134,35 @@ function declare(request) {
|
|
|
17838
18134
|
if (problem) return refused(alias, problem);
|
|
17839
18135
|
const declaration2 = declarationOf(request, read2.source);
|
|
17840
18136
|
const declared = request.project ?? alias;
|
|
17841
|
-
|
|
18137
|
+
let binding;
|
|
18138
|
+
let bindProblem = "it could not be bound";
|
|
17842
18139
|
try {
|
|
17843
|
-
|
|
18140
|
+
binding = resolveCandidate(alias, declaration2);
|
|
17844
18141
|
} catch (e) {
|
|
17845
|
-
|
|
18142
|
+
bindProblem = e instanceof Error ? e.message : String(e);
|
|
17846
18143
|
}
|
|
17847
|
-
const
|
|
17848
|
-
const answered = binding ? readProducer(binding) : { why: "it is not among the resolved externals" };
|
|
18144
|
+
const answered = binding ? readProducer(binding) : { why: bindProblem };
|
|
17849
18145
|
const producer = "read" in answered ? answered.read : null;
|
|
17850
18146
|
const contradicted = contradiction(request, binding, producer);
|
|
17851
|
-
if (contradicted)
|
|
17852
|
-
|
|
17853
|
-
|
|
18147
|
+
if (contradicted) return refused(alias, contradicted);
|
|
18148
|
+
const why2 = "why" in answered ? answered.why : "unknown";
|
|
18149
|
+
if (request.dryRun) {
|
|
18150
|
+
return {
|
|
18151
|
+
alias,
|
|
18152
|
+
written: false,
|
|
18153
|
+
declaration: declaration2,
|
|
18154
|
+
project: producer?.id ?? declared,
|
|
18155
|
+
...producer ? {} : { unreachable: `its producer could not be read (${why2}), so nothing would be pinned` }
|
|
18156
|
+
};
|
|
18157
|
+
}
|
|
18158
|
+
try {
|
|
18159
|
+
declareExternal(alias, declaration2);
|
|
18160
|
+
} catch (e) {
|
|
18161
|
+
return refused(alias, (e instanceof Error ? e.message : String(e)).replace(/\.$/, ""));
|
|
17854
18162
|
}
|
|
17855
18163
|
let pin2;
|
|
17856
18164
|
if (request.pin !== false && producer) pin2 = pinExternals([alias])[0];
|
|
17857
|
-
const unreachable = producer ? void 0 : `its producer could not be read (${
|
|
18165
|
+
const unreachable = producer ? void 0 : `its producer could not be read (${why2}), so nothing was pinned \u2014 it reads as unavailable, never a pass, until \`wairon externals pin ${alias}\` succeeds`;
|
|
17858
18166
|
return {
|
|
17859
18167
|
alias,
|
|
17860
18168
|
written: true,
|
|
@@ -17864,6 +18172,77 @@ function declare(request) {
|
|
|
17864
18172
|
...unreachable ? { unreachable } : {}
|
|
17865
18173
|
};
|
|
17866
18174
|
}
|
|
18175
|
+
function remove(alias, dryRun) {
|
|
18176
|
+
const config = loadProjectConfig();
|
|
18177
|
+
const declared = config?.externals?.[alias] !== void 0;
|
|
18178
|
+
const rows = !declared || dryRun ? listExternals() : [];
|
|
18179
|
+
const pinned = rows.some((r) => r.alias === alias && r.lock !== void 0);
|
|
18180
|
+
if (!declared && !pinned) {
|
|
18181
|
+
const known = rows.map((r) => `"${r.alias}"`);
|
|
18182
|
+
return {
|
|
18183
|
+
alias,
|
|
18184
|
+
removed: false,
|
|
18185
|
+
unpinned: false,
|
|
18186
|
+
refusal: `The external "${alias}" was not removed: it is neither declared nor pinned here \u2014 ${known.length ? `this project's externals are ${known.join(", ")}` : "this project declares no externals"}.`
|
|
18187
|
+
};
|
|
18188
|
+
}
|
|
18189
|
+
if (dryRun) return { alias, removed: false, unpinned: pinned };
|
|
18190
|
+
const removed = declared ? removeExternal(alias) : false;
|
|
18191
|
+
const unpinned = unpin(alias);
|
|
18192
|
+
return { alias, removed, unpinned };
|
|
18193
|
+
}
|
|
18194
|
+
function currentUse(declaration2) {
|
|
18195
|
+
const use = typeof declaration2 === "object" && declaration2 !== null ? declaration2.use : void 0;
|
|
18196
|
+
return Array.isArray(use) ? use.filter((u) => typeof u === "string") : [];
|
|
18197
|
+
}
|
|
18198
|
+
function useRefused(alias, use, why2) {
|
|
18199
|
+
return { alias, written: false, use, added: [], removed: [], refusal: `The \`use\` of "${alias}" was not changed: ${why2}.` };
|
|
18200
|
+
}
|
|
18201
|
+
function updateUse(request) {
|
|
18202
|
+
const { alias } = request;
|
|
18203
|
+
const config = loadProjectConfig();
|
|
18204
|
+
const existing = config?.externals?.[alias];
|
|
18205
|
+
const current2 = currentUse(existing);
|
|
18206
|
+
if (existing === void 0) {
|
|
18207
|
+
const declared = Object.keys(config?.externals ?? {});
|
|
18208
|
+
return useRefused(alias, current2, `"${alias}" is not a declared external \u2014 ${declared.length ? `this project declares ${declared.map((a) => `"${a}"`).join(", ")}` : "this project declares no externals"} (declare it with \`wairon externals add\`)`);
|
|
18209
|
+
}
|
|
18210
|
+
const add2 = [...new Set(request.add ?? [])];
|
|
18211
|
+
const drop = [...new Set(request.remove ?? [])];
|
|
18212
|
+
const bad = [...add2, ...drop].find((u) => !USE_ENTRY_RE.test(u));
|
|
18213
|
+
if (bad !== void 0) return useRefused(alias, current2, `the entry "${bad}" is neither \`*\` nor a public name ([a-z0-9-_]+)`);
|
|
18214
|
+
if (add2.length === 0 && drop.length === 0) return useRefused(alias, current2, "name at least one public name to add or to remove");
|
|
18215
|
+
const kept = current2.filter((u) => !drop.includes(u));
|
|
18216
|
+
const appended = kept.includes("*") ? [] : add2.filter((u) => !kept.includes(u));
|
|
18217
|
+
const next = [...kept, ...appended];
|
|
18218
|
+
const removedNames = current2.filter((u) => drop.includes(u));
|
|
18219
|
+
const named2 = appended.filter((u) => u !== "*");
|
|
18220
|
+
if (named2.length > 0) {
|
|
18221
|
+
const binding = resolveDeclared(true).find((b) => b.external.alias === alias);
|
|
18222
|
+
const answered = binding ? readProducer(binding) : null;
|
|
18223
|
+
if (answered && "read" in answered) {
|
|
18224
|
+
const missing = named2.filter((u) => !answered.read.exported.includes(u));
|
|
18225
|
+
if (missing.length) return useRefused(alias, current2, missing.map((u) => unexportedReason(u, answered.read)).join("; "));
|
|
18226
|
+
}
|
|
18227
|
+
}
|
|
18228
|
+
const same = next.length === current2.length && next.every((u, i) => u === current2[i]);
|
|
18229
|
+
if (request.dryRun || same) return { alias, written: false, use: next, added: appended, removed: removedNames };
|
|
18230
|
+
if (removedNames.length > 0) {
|
|
18231
|
+
const original = typeof existing === "object" && existing !== null ? existing : {};
|
|
18232
|
+
const { use: _old, ...rest } = original;
|
|
18233
|
+
const restated = { ...rest, ...next.length ? { use: next } : {} };
|
|
18234
|
+
removeExternal(alias);
|
|
18235
|
+
try {
|
|
18236
|
+
declareExternal(alias, restated);
|
|
18237
|
+
} catch (e) {
|
|
18238
|
+
declareExternal(alias, original);
|
|
18239
|
+
return useRefused(alias, current2, (e instanceof Error ? e.message : String(e)).replace(/\.$/, ""));
|
|
18240
|
+
}
|
|
18241
|
+
return { alias, written: true, use: next, added: appended, removed: removedNames };
|
|
18242
|
+
}
|
|
18243
|
+
importNames(alias, appended);
|
|
18244
|
+
return { alias, written: true, use: next, added: appended, removed: removedNames };
|
|
18245
|
+
}
|
|
17867
18246
|
|
|
17868
18247
|
// src/core/surface-portal.ts
|
|
17869
18248
|
function exportSurface2(maxAudience, format, outPath, portalId) {
|
|
@@ -17902,6 +18281,12 @@ function declareExternal2(request) {
|
|
|
17902
18281
|
function pinnedParent2() {
|
|
17903
18282
|
return pinnedParent();
|
|
17904
18283
|
}
|
|
18284
|
+
function removeExternal2(alias, dryRun) {
|
|
18285
|
+
return remove(alias, dryRun);
|
|
18286
|
+
}
|
|
18287
|
+
function updateExternalUse(request) {
|
|
18288
|
+
return updateUse(request);
|
|
18289
|
+
}
|
|
17905
18290
|
|
|
17906
18291
|
// src/core/family-validation.ts
|
|
17907
18292
|
var fs11 = __toESM(require("fs"));
|
|
@@ -18009,11 +18394,18 @@ function findingChanges(before, after) {
|
|
|
18009
18394
|
}
|
|
18010
18395
|
|
|
18011
18396
|
// src/core/rules/reach-model-projector.ts
|
|
18012
|
-
function
|
|
18397
|
+
function underBase(basePath, path49) {
|
|
18398
|
+
const base = (basePath ?? "").trim().replace(/\/+$/, "");
|
|
18399
|
+
if (base === "" || base === "/") return path49;
|
|
18400
|
+
const head2 = base.startsWith("/") ? base : `/${base}`;
|
|
18401
|
+
if (path49 === head2 || path49.startsWith(`${head2}/`)) return path49;
|
|
18402
|
+
return `${head2}${path49.startsWith("/") ? "" : "/"}${path49}`;
|
|
18403
|
+
}
|
|
18404
|
+
function bindingLine(endpoint, basePath) {
|
|
18013
18405
|
if (!endpoint) return void 0;
|
|
18014
18406
|
switch (endpoint.transport) {
|
|
18015
18407
|
case "HTTP":
|
|
18016
|
-
return `${endpoint.method} ${endpoint.path}`;
|
|
18408
|
+
return `${endpoint.method} ${underBase(basePath, endpoint.path)}`;
|
|
18017
18409
|
case "gRPC":
|
|
18018
18410
|
return `${endpoint.service}/${endpoint.method}`;
|
|
18019
18411
|
case "GraphQL":
|
|
@@ -18053,6 +18445,12 @@ function widestAudience(ctx, portal, interfaceId) {
|
|
|
18053
18445
|
function byVerb(a, b) {
|
|
18054
18446
|
return a.project.localeCompare(b.project) || a.portal.localeCompare(b.portal) || a.verb.localeCompare(b.verb);
|
|
18055
18447
|
}
|
|
18448
|
+
function byPlacement(a, b) {
|
|
18449
|
+
return a.project.localeCompare(b.project) || a.component.localeCompare(b.component);
|
|
18450
|
+
}
|
|
18451
|
+
function byTopic(a, b) {
|
|
18452
|
+
return a.component.localeCompare(b.component) || a.topic.localeCompare(b.topic) || Number(a.emits) - Number(b.emits);
|
|
18453
|
+
}
|
|
18056
18454
|
function byCall(a, b) {
|
|
18057
18455
|
return a.fromProject.localeCompare(b.fromProject) || a.fromComponent.localeCompare(b.fromComponent) || a.toPortal.localeCompare(b.toPortal) || a.verb.localeCompare(b.verb) || a.evidence.localeCompare(b.evidence);
|
|
18058
18456
|
}
|
|
@@ -18068,7 +18466,7 @@ function project(ctx, network) {
|
|
|
18068
18466
|
for (const m of intf.methods) {
|
|
18069
18467
|
const entry = componentEntryFor(comp, m.invokedBy);
|
|
18070
18468
|
if (isOutsideEntry(entry)) outsideEntered = true;
|
|
18071
|
-
const binding = comp.transport === "InProcess" ? void 0 : bindingLine(m.endpoint);
|
|
18469
|
+
const binding = comp.transport === "InProcess" ? void 0 : bindingLine(m.endpoint, comp.basePath);
|
|
18072
18470
|
const audience = widestAudience(ctx, comp.id, intf.id);
|
|
18073
18471
|
verbs.push({
|
|
18074
18472
|
project: "",
|
|
@@ -18117,11 +18515,32 @@ function project(ctx, network) {
|
|
|
18117
18515
|
}
|
|
18118
18516
|
}
|
|
18119
18517
|
}
|
|
18518
|
+
const placements = ctx.components.filter(isOwn2).map((c) => ({
|
|
18519
|
+
project: "",
|
|
18520
|
+
component: c.id,
|
|
18521
|
+
...c.subsystem !== void 0 ? { subsystem: c.subsystem } : {},
|
|
18522
|
+
componentType: c.componentType
|
|
18523
|
+
}));
|
|
18524
|
+
const topics = [];
|
|
18525
|
+
const addEnd = (component, topic, emits) => {
|
|
18526
|
+
if (!topics.some((t) => t.component === component && t.topic === topic && t.emits === emits)) topics.push({ component, topic, emits });
|
|
18527
|
+
};
|
|
18528
|
+
for (const comp of ctx.components.filter(isOwn2)) {
|
|
18529
|
+
for (const e of comp.emits ?? []) addEnd(comp.id, e.topic, true);
|
|
18530
|
+
for (const e of comp.subscribesTo ?? []) addEnd(comp.id, e.topic, false);
|
|
18531
|
+
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
18532
|
+
for (const m of intf.methods) {
|
|
18533
|
+
if (m.endpoint?.transport === "MessageBus") addEnd(comp.id, m.endpoint.topic, m.endpoint.direction === "publish");
|
|
18534
|
+
}
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
18120
18537
|
return {
|
|
18121
18538
|
scope: "own",
|
|
18122
18539
|
networks: ownNetwork ? [{ ...ownNetwork, gateways: [...ownNetwork.gateways].sort() }] : [],
|
|
18123
18540
|
verbs: verbs.sort(byVerb),
|
|
18124
|
-
calls: calls.sort(byCall)
|
|
18541
|
+
calls: calls.sort(byCall),
|
|
18542
|
+
placements: placements.sort(byPlacement),
|
|
18543
|
+
topics: topics.sort(byTopic)
|
|
18125
18544
|
};
|
|
18126
18545
|
}
|
|
18127
18546
|
function qualify(projectKey, local) {
|
|
@@ -18156,21 +18575,32 @@ function compose(models, family, networks) {
|
|
|
18156
18575
|
};
|
|
18157
18576
|
const verbs = [];
|
|
18158
18577
|
const calls = [];
|
|
18578
|
+
const placements = [];
|
|
18579
|
+
const topics = [];
|
|
18159
18580
|
for (const [key2, model] of models) {
|
|
18581
|
+
for (const p of model.placements ?? []) {
|
|
18582
|
+
placements.push({
|
|
18583
|
+
...p,
|
|
18584
|
+
project: key2,
|
|
18585
|
+
component: qualify(key2, p.component),
|
|
18586
|
+
...p.subsystem !== void 0 ? { subsystem: qualify(key2, p.subsystem) } : {}
|
|
18587
|
+
});
|
|
18588
|
+
}
|
|
18589
|
+
for (const t of model.topics ?? []) topics.push({ ...t, component: qualify(key2, t.component) });
|
|
18160
18590
|
const net = innermost(key2);
|
|
18161
18591
|
for (const v of model.verbs) {
|
|
18162
18592
|
const { network: _own, ...rest } = v;
|
|
18163
|
-
const
|
|
18593
|
+
const placed2 = {
|
|
18164
18594
|
...rest,
|
|
18165
18595
|
project: key2,
|
|
18166
18596
|
portal: qualify(key2, v.portal),
|
|
18167
18597
|
...v.subsystem !== void 0 ? { subsystem: qualify(key2, v.subsystem) } : {},
|
|
18168
18598
|
...net !== void 0 ? { network: net } : {}
|
|
18169
18599
|
};
|
|
18170
|
-
verbs.push(
|
|
18171
|
-
if (net !== void 0 &&
|
|
18600
|
+
verbs.push(placed2);
|
|
18601
|
+
if (net !== void 0 && placed2.gateway && isOutsideEntry(placed2.entry)) {
|
|
18172
18602
|
const gateways = boundaries.get(net).gateways;
|
|
18173
|
-
if (!gateways.includes(
|
|
18603
|
+
if (!gateways.includes(placed2.portal)) gateways.push(placed2.portal);
|
|
18174
18604
|
}
|
|
18175
18605
|
}
|
|
18176
18606
|
for (const c of model.calls) {
|
|
@@ -18186,15 +18616,17 @@ function compose(models, family, networks) {
|
|
|
18186
18616
|
}
|
|
18187
18617
|
}
|
|
18188
18618
|
const portals = new Set(verbs.map((v) => v.portal));
|
|
18619
|
+
const placed = new Map(placements.map((p) => [p.component, p]));
|
|
18189
18620
|
for (const ref of family.references) {
|
|
18190
18621
|
if (!["call", "calls", "register", "dispatch"].includes(ref.position) || ref.member === void 0) continue;
|
|
18191
18622
|
if (!portals.has(ref.target)) continue;
|
|
18192
18623
|
const net = innermost(ref.consumer);
|
|
18624
|
+
const caller = ref.caller ?? ref.specId;
|
|
18625
|
+
const subsystem = placed.get(caller)?.subsystem;
|
|
18193
18626
|
calls.push({
|
|
18194
18627
|
fromProject: ref.consumer,
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
fromComponent: ref.caller ?? ref.specId,
|
|
18628
|
+
fromComponent: caller,
|
|
18629
|
+
...subsystem !== void 0 ? { fromSubsystem: subsystem } : {},
|
|
18198
18630
|
...net !== void 0 ? { fromNetwork: net } : {},
|
|
18199
18631
|
toPortal: ref.target,
|
|
18200
18632
|
verb: ref.member,
|
|
@@ -18205,7 +18637,9 @@ function compose(models, family, networks) {
|
|
|
18205
18637
|
scope: "family",
|
|
18206
18638
|
networks: [...boundaries.values()].map((b) => ({ ...b, gateways: [...b.gateways].sort() })).sort((a, b) => depth(a.id) - depth(b.id) || a.id.localeCompare(b.id)),
|
|
18207
18639
|
verbs: verbs.sort(byVerb),
|
|
18208
|
-
calls: calls.sort(byCall)
|
|
18640
|
+
calls: calls.sort(byCall),
|
|
18641
|
+
placements: placements.sort(byPlacement),
|
|
18642
|
+
topics: topics.sort(byTopic)
|
|
18209
18643
|
};
|
|
18210
18644
|
}
|
|
18211
18645
|
|
|
@@ -18495,6 +18929,8 @@ function run(options) {
|
|
|
18495
18929
|
issues.push(...judged.own.issues.map((i) => ({ ...i, project: ref.key })), ...judged.composed.findings);
|
|
18496
18930
|
projects.push(verdictOf({ namespace: ref.key, directory: ref.directory }, judged.own));
|
|
18497
18931
|
}
|
|
18932
|
+
const paired = pairTopicsAcrossFamily(issues, models);
|
|
18933
|
+
issues.splice(0, issues.length, ...paired);
|
|
18498
18934
|
issues.push(...within2(root, ceiling, () => checkMembers(narrowed(family, selected))));
|
|
18499
18935
|
issues.push(...within2(root, ceiling, () => checkPolicies(narrowed(family, selected))));
|
|
18500
18936
|
issues.push(...within2(root, ceiling, () => checkReach(narrowed(family, selected), models)));
|
|
@@ -18523,7 +18959,11 @@ function howMoved(use) {
|
|
|
18523
18959
|
const changed = use.detail?.includes("signature changed") ? " (and its signature changed)" : "";
|
|
18524
18960
|
return `was renamed to "${use.renamedTo}"${changed}, per the producer's rename trace`;
|
|
18525
18961
|
}
|
|
18526
|
-
return
|
|
18962
|
+
return `is gone from the producer's live export table${use.detail ? ` (${use.detail})` : ""}`;
|
|
18963
|
+
}
|
|
18964
|
+
function staleTail(status) {
|
|
18965
|
+
const facts = status.staleFacts ?? [];
|
|
18966
|
+
return facts.length ? ` Its pinned snapshot is stale: ${facts.join(", ")} changed in the producer, and the rules judge the pin (a library's abi, an entry's transport or role) \u2014 re-pinning refreshes it.` : "";
|
|
18527
18967
|
}
|
|
18528
18968
|
function judgeExternal(status, project3, config) {
|
|
18529
18969
|
const out = [];
|
|
@@ -18538,11 +18978,11 @@ function judgeExternal(status, project3, config) {
|
|
|
18538
18978
|
project3
|
|
18539
18979
|
));
|
|
18540
18980
|
}
|
|
18541
|
-
if (broken.length === 0 && status.drifted) {
|
|
18981
|
+
if (broken.length === 0 && (status.drifted || (status.staleFacts?.length ?? 0) > 0)) {
|
|
18542
18982
|
out.push(finding(
|
|
18543
18983
|
config,
|
|
18544
18984
|
"EXTERNAL_DRIFTED",
|
|
18545
|
-
`The producer of ${who} changed since ${named(project3)} pinned it, but nothing ${named(project3)} uses changed
|
|
18985
|
+
`The producer of ${who} changed since ${named(project3)} pinned it, but nothing ${named(project3)} uses changed.${staleTail(status)} Re-pin when convenient (\`wairon externals pin ${status.alias}\`).`,
|
|
18546
18986
|
project3
|
|
18547
18987
|
));
|
|
18548
18988
|
}
|
|
@@ -18597,18 +19037,18 @@ function adviseOn(status, family, config) {
|
|
|
18597
19037
|
case "incompatible": {
|
|
18598
19038
|
const broken = status.uses.filter(isBroken);
|
|
18599
19039
|
const users = [...new Set(broken.flatMap((u) => specsUsing(family, status.alias, u.publicName)))].sort();
|
|
18600
|
-
const
|
|
19040
|
+
const renamed2 = broken.some((u) => u.state === "renamed");
|
|
18601
19041
|
return advisory(
|
|
18602
19042
|
config,
|
|
18603
19043
|
"EXTERNAL_LIVE_INCOMPATIBLE",
|
|
18604
|
-
`${who} moved in its live producer since it was pinned: ${broken.map(movedUse).join("; ")}. Used by ${users.length ? users.map((s) => `"${s}"`).join(", ") : "this project's references"}. Adapt the uses${
|
|
19044
|
+
`${who} moved in its live producer since it was pinned: ${broken.map(movedUse).join("; ")}. Used by ${users.length ? users.map((s) => `"${s}"`).join(", ") : "this project's references"}. Adapt the uses${renamed2 ? " (follow the rename)" : ""}, then re-pin with ${repin(status.alias)}. Advisory: the pin still gates.`
|
|
18605
19045
|
);
|
|
18606
19046
|
}
|
|
18607
19047
|
case "drifted":
|
|
18608
19048
|
return advisory(
|
|
18609
19049
|
config,
|
|
18610
19050
|
"EXTERNAL_DRIFTED",
|
|
18611
|
-
`The live producer of ${who} moved since it was pinned, but nothing this project uses changed
|
|
19051
|
+
`The live producer of ${who} moved since it was pinned, but nothing this project uses changed.${staleTail(status)} Re-pin when convenient (${repin(status.alias)}).`
|
|
18612
19052
|
);
|
|
18613
19053
|
case "unavailable": {
|
|
18614
19054
|
const reason = uncomparedReason(status);
|
|
@@ -19138,7 +19578,51 @@ function reachModel(options) {
|
|
|
19138
19578
|
models.set(node.namespace, within2(node.directory, ceiling, () => ownReachOf(options)));
|
|
19139
19579
|
}
|
|
19140
19580
|
const scoped = narrowed(family, selected);
|
|
19141
|
-
return within2(root, ceiling, () =>
|
|
19581
|
+
return within2(root, ceiling, () => {
|
|
19582
|
+
const model = compose(models, scoped, networksOf(scoped));
|
|
19583
|
+
return { ...model, findings: judgedFindings(model, scoped) };
|
|
19584
|
+
});
|
|
19585
|
+
}
|
|
19586
|
+
function judgedFindings(model, family) {
|
|
19587
|
+
const nodeOf = new Map(family.nodes.map((n) => [n.namespace, n]));
|
|
19588
|
+
const configs = /* @__PURE__ */ new Map();
|
|
19589
|
+
const allows = /* @__PURE__ */ new Map();
|
|
19590
|
+
const out = [];
|
|
19591
|
+
for (const f of judge(model)) {
|
|
19592
|
+
const project3 = projectOfFinding(model, f);
|
|
19593
|
+
const node = nodeOf.get(project3);
|
|
19594
|
+
if (!configs.has(project3)) configs.set(project3, node ? runWithProjectRoot(node.directory, configOrNull) : null);
|
|
19595
|
+
if (!allows.has(project3)) allows.set(project3, node ? runWithProjectRoot(node.directory, lintAllows) : []);
|
|
19596
|
+
const local = f.specId !== void 0 ? localKey(project3, f.specId) : void 0;
|
|
19597
|
+
const tuned = finding(configs.get(project3) ?? null, f.code, f.message, project3, f.specId);
|
|
19598
|
+
if (!tuned) continue;
|
|
19599
|
+
const allowed = tuned.severity !== "error" && local !== void 0 && (allows.get(project3) ?? []).some((a) => a.specId === local && a.code === f.code && (a.at ?? void 0) === (f.at ?? void 0));
|
|
19600
|
+
if (allowed) continue;
|
|
19601
|
+
out.push({
|
|
19602
|
+
severity: tuned.severity,
|
|
19603
|
+
code: f.code,
|
|
19604
|
+
message: f.message,
|
|
19605
|
+
...f.specId !== void 0 ? { specId: f.specId } : {},
|
|
19606
|
+
...f.at !== void 0 ? { at: f.at } : {}
|
|
19607
|
+
});
|
|
19608
|
+
}
|
|
19609
|
+
return out;
|
|
19610
|
+
}
|
|
19611
|
+
function pairTopicsAcrossFamily(issues, models) {
|
|
19612
|
+
const ends = [...models.entries()].flatMap(([project3, m]) => (m.topics ?? []).map((t) => ({ ...t, project: project3 })));
|
|
19613
|
+
if (ends.length === 0) return [...issues];
|
|
19614
|
+
const pairedElsewhere = (project3, component, emits) => {
|
|
19615
|
+
const own2 = ends.filter((e) => e.project === project3);
|
|
19616
|
+
const mine = own2.filter((e) => e.component === component && e.emits === emits).map((e) => e.topic);
|
|
19617
|
+
const unpaired = mine.filter((t) => !own2.some((o) => o.topic === t && o.emits !== emits));
|
|
19618
|
+
if (unpaired.length === 0) return false;
|
|
19619
|
+
return unpaired.every((t) => ends.some((o) => o.project !== project3 && o.topic === t && o.emits !== emits));
|
|
19620
|
+
};
|
|
19621
|
+
return issues.filter((i) => {
|
|
19622
|
+
if (i.code !== "UNCONSUMED_TOPIC" && i.code !== "UNSOURCED_SUBSCRIPTION") return true;
|
|
19623
|
+
if (i.project === void 0 || i.specId === void 0) return true;
|
|
19624
|
+
return !pairedElsewhere(i.project, i.specId, i.code === "UNCONSUMED_TOPIC");
|
|
19625
|
+
});
|
|
19142
19626
|
}
|
|
19143
19627
|
|
|
19144
19628
|
// src/core/rules/index.ts
|
|
@@ -21096,8 +21580,8 @@ var publicSurfaceBindingRule = {
|
|
|
21096
21580
|
ctx.addIssue("error", "PUBLIC_INTERFACE_INVALID_COMPONENT", `Subsystem "${sub.id}" public interface references component "${pi.component}" which does not exist.`, sub.id, isDraftCtx);
|
|
21097
21581
|
continue;
|
|
21098
21582
|
}
|
|
21099
|
-
const
|
|
21100
|
-
if (!
|
|
21583
|
+
const isSubsystemOwner2 = backing.subsystem === sub.id || backing.subsystem.startsWith(sub.id + "::");
|
|
21584
|
+
if (!isSubsystemOwner2) {
|
|
21101
21585
|
ctx.addIssue("error", "PUBLIC_INTERFACE_FOREIGN_COMPONENT", `Subsystem "${sub.id}" publishes component "${pi.component}", but it belongs to subsystem "${backing.subsystem}". A subsystem may only publish its own components.`, sub.id, isDraftCtx);
|
|
21102
21586
|
}
|
|
21103
21587
|
}
|
|
@@ -21136,19 +21620,23 @@ var publicSurfaceConsumersRule = {
|
|
|
21136
21620
|
var implementsContractsRule = {
|
|
21137
21621
|
name: "implements-contracts",
|
|
21138
21622
|
judges: "design",
|
|
21139
|
-
description: "An interface that declares `implements: alias::name` realizes another project's extension point, so it must declare every method of that contract with the same params and returns, read from the export the reference resolves to (a contained member's live table, or a declared external's pin). Whether the reference resolves and names an entry exported with role implement is project-boundaries' finding (EXTERNAL_NOT_EXPORTED); this rule judges the shape once it resolves. Extra methods are the implementer's own and are not reported.",
|
|
21623
|
+
description: "An interface that declares `implements: alias::name` realizes another project's extension point, so it must declare every method of that contract with the same params and returns, read from the export the reference resolves to (a contained member's live table, or a declared external's pin). Types are compared semantically, never by spelling: the producer's own bare name and the consumer's `alias::name` (or the bare name its `use` imports) are one type. Whether the reference resolves and names an entry exported with role implement is project-boundaries' finding (EXTERNAL_NOT_EXPORTED); this rule judges the shape once it resolves. Extra methods are the implementer's own and are not reported.",
|
|
21140
21624
|
codes: [
|
|
21141
21625
|
{ code: "IMPLEMENTS_MISMATCH", defaultSeverity: "error", summary: "An interface implementing an extension point lacks one of its methods, or declares one with different params or returns" }
|
|
21142
21626
|
],
|
|
21143
21627
|
check(ctx) {
|
|
21144
21628
|
for (const intf of ctx.interfaces) {
|
|
21145
21629
|
if (intf.implements === void 0 || !ctx.isSpecInScope(intf.id)) continue;
|
|
21146
|
-
const
|
|
21147
|
-
if (!
|
|
21630
|
+
const point = extensionPoint(ctx, intf.id, intf.implements);
|
|
21631
|
+
if (!point) continue;
|
|
21632
|
+
const { methods: contract, publicNames } = point;
|
|
21633
|
+
const alias = intf.implements.split("::")[0];
|
|
21634
|
+
const expectedType = (t) => respelled(t, (n) => publicNames.get(lastName(n)) ?? lastName(n));
|
|
21635
|
+
const ownType = (t) => respelled(t, (n) => n.startsWith(`${alias}::`) ? lastName(n.slice(alias.length + 2)) : n);
|
|
21148
21636
|
const draft = intf.status === "draft" || intf.status === "design" || ctx.isComponentDraft(intf.component);
|
|
21149
21637
|
for (const expected of contract) {
|
|
21150
21638
|
const own2 = intf.methods.find((m) => m.name === expected.name);
|
|
21151
|
-
const differs = own2 ? difference(expected, own2) : "is not declared";
|
|
21639
|
+
const differs = own2 ? difference(expected, own2, expectedType, ownType) : "is not declared";
|
|
21152
21640
|
if (!differs) continue;
|
|
21153
21641
|
ctx.addIssue(
|
|
21154
21642
|
"error",
|
|
@@ -21163,17 +21651,36 @@ var implementsContractsRule = {
|
|
|
21163
21651
|
}
|
|
21164
21652
|
}
|
|
21165
21653
|
};
|
|
21166
|
-
function
|
|
21167
|
-
|
|
21168
|
-
|
|
21169
|
-
|
|
21654
|
+
function lastName(name) {
|
|
21655
|
+
return name.split("::").pop().split(".").pop();
|
|
21656
|
+
}
|
|
21657
|
+
function renamed(expr, rename) {
|
|
21658
|
+
const args = expr.args.map((a) => renamed(a, rename));
|
|
21659
|
+
return expr.form === "named" || expr.form === "applied" ? { ...expr, name: rename(expr.name), args } : { ...expr, args };
|
|
21660
|
+
}
|
|
21661
|
+
function respelled(text3, rename) {
|
|
21662
|
+
const keep = (name) => isTypeVocabulary(name) ? name : rename(name);
|
|
21663
|
+
const parsed = parseTypeExpression(text3, "returns");
|
|
21664
|
+
if (parsed.expression) return canonicalTypeText(renamed(parsed.expression, keep));
|
|
21665
|
+
return text3.replace(/[A-Za-z_][A-Za-z0-9_-]*(?:(?:::|\.)[A-Za-z_][A-Za-z0-9_-]*)*/g, keep);
|
|
21666
|
+
}
|
|
21667
|
+
function difference(expected, own2, expectedType, ownType) {
|
|
21668
|
+
const shape = (m, as) => (m.params ?? []).map((p) => `${as(p.type)}${p.optional ? "?" : ""}`).join(", ");
|
|
21669
|
+
const written = (m) => (m.params ?? []).map((p) => `${p.type}${p.optional ? "?" : ""}`).join(", ");
|
|
21670
|
+
if (shape(expected, expectedType) !== shape(own2, ownType)) return `params (${written(own2)}) instead of (${written(expected)})`;
|
|
21671
|
+
if (expectedType(expected.returns) !== ownType(own2.returns)) return `returns ${own2.returns} instead of ${expected.returns}`;
|
|
21170
21672
|
return void 0;
|
|
21171
21673
|
}
|
|
21172
21674
|
function extensionPoint(ctx, specId, ref) {
|
|
21173
21675
|
const resolution = ctx.resolveCrossProject(specId, "implements", ref);
|
|
21174
21676
|
if (resolution && resolution.outcome !== "resolved") return void 0;
|
|
21175
21677
|
const surface = ctx.resolveSurfaceRef(ref);
|
|
21176
|
-
if (surface.kind === "resolved")
|
|
21678
|
+
if (surface.kind === "resolved") {
|
|
21679
|
+
if (surface.entry.role !== "implement") return void 0;
|
|
21680
|
+
const publicNames2 = /* @__PURE__ */ new Map();
|
|
21681
|
+
for (const t of surface.snapshot.exportedTypes ?? []) publicNames2.set(lastName(t.type), t.id);
|
|
21682
|
+
return { methods: surface.entry.methods, publicNames: publicNames2 };
|
|
21683
|
+
}
|
|
21177
21684
|
const [alias, name] = ref.split("::");
|
|
21178
21685
|
if (name === void 0) return void 0;
|
|
21179
21686
|
const bound2 = ctx.projectFamily?.nodes.find((n) => n.namespace === "");
|
|
@@ -21182,8 +21689,13 @@ function extensionPoint(ctx, specId, ref) {
|
|
|
21182
21689
|
const table = (ctx.exportTables ?? []).find((t) => t.level === "project" && t.owner === key2);
|
|
21183
21690
|
const entry = table?.entries.find((e) => e.kind === "component" && e.publicName === name);
|
|
21184
21691
|
if (!entry || entry.role !== "implement" || !entry.component) return void 0;
|
|
21185
|
-
|
|
21186
|
-
|
|
21692
|
+
const methods = entry.interface ? ctx.interfaceMap.get(entry.interface)?.methods : ctx.interfaceMethodsOf(entry.component);
|
|
21693
|
+
if (!methods) return void 0;
|
|
21694
|
+
const publicNames = /* @__PURE__ */ new Map();
|
|
21695
|
+
for (const e of table?.entries ?? []) {
|
|
21696
|
+
if (e.kind === "type" && e.typeDef) publicNames.set(lastName(e.typeDef), e.publicName);
|
|
21697
|
+
}
|
|
21698
|
+
return { methods, publicNames };
|
|
21187
21699
|
}
|
|
21188
21700
|
|
|
21189
21701
|
// src/core/rules/integrity/public-surface-bound-contract.ts
|
|
@@ -21215,13 +21727,14 @@ var publicSurfaceBoundContractRule = {
|
|
|
21215
21727
|
var exportTablesRule = {
|
|
21216
21728
|
name: "export-tables",
|
|
21217
21729
|
judges: "design",
|
|
21218
|
-
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 (or two explicit entries) bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out. No chain leads back to itself: EXPORT_CYCLE, 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 (a subsystem-owned type at L1, a project-level type with no subsystem for an L0 `{ typeDef }` without `from`), under a public name of [a-z0-9-_]+ (EXPORT_INVALID). Every exported component can serve what its role promises (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports): an entry consumers call must be a Portal (a gateway being a Portal variant) or an Observer for events, and an entry consumers implement (role implement, an extension point) may also be an Adapter whose realization consumers supply. An L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. An entry's export kind is derived from its backing Portal's transport and no longer authored, so no rule compares an authored kind with the stereotype; the retired public-surface-declared-type rule and its codes are gone. The rule judges the facts the export resolver recorded in the scan, for the bound root's table and every contained member's, and resolves nothing itself.",
|
|
21730
|
+
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 (or two explicit entries) bind a name to different targets, or two public names share one nameKey, since a bare import could not tell them apart; the name is left out. No chain leads back to itself: EXPORT_CYCLE, 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 (a subsystem-owned type at L1, a project-level type with no subsystem for an L0 `{ typeDef }` without `from`), under a public name of [a-z0-9-_]+ (EXPORT_INVALID) \u2014 a named re-export whose source renamed the item names the new name. Every exported component can serve what its role promises (EXPORT_UNCONSUMABLE, at L0 and for L1 re-exports): an entry consumers call must be a Portal (a gateway being a Portal variant) or an Observer for events, and an entry consumers implement (role implement, an extension point) may also be an Adapter whose realization consumers supply. An L0 entry that re-exports a member's or an external's export never declares a wider audience than that export has (EXPORT_WIDENS_AUDIENCE): a re-export can narrow reach, never widen it. An own L0 entry exported to `department` is a notice (EXPORT_AUDIENCE_NARROW): outside a hosted instance only the project's family sees it, and a sibling checkout or git consumer \u2014 read at `instance` \u2014 is refused; whether its consumers are hosted cannot be told from the tree, so it never fails the gate. An entry's export kind is derived from its backing Portal's transport and no longer authored, so no rule compares an authored kind with the stereotype; the retired public-surface-declared-type rule and its codes are gone. The rule judges the facts the export resolver recorded in the scan, for the bound root's table and every contained member's, and resolves nothing itself.",
|
|
21219
21731
|
codes: [
|
|
21220
21732
|
{ code: "EXPORT_ID_DUPLICATE", defaultSeverity: "error", summary: "One public name bound to different targets by two wildcard re-exports or two explicit entries" },
|
|
21221
21733
|
{ 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)" },
|
|
21222
21734
|
{ code: "EXPORT_INVALID", defaultSeverity: "error", 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" },
|
|
21223
21735
|
{ code: "EXPORT_UNCONSUMABLE", defaultSeverity: "error", summary: "An exported component that cannot serve its role: called but neither a Portal nor an Observer, or implemented but neither a Portal nor an Adapter" },
|
|
21224
|
-
{ code: "EXPORT_WIDENS_AUDIENCE", defaultSeverity: "error", summary: "An L0 entry declares a wider audience than the member or external export it re-exports" }
|
|
21736
|
+
{ code: "EXPORT_WIDENS_AUDIENCE", defaultSeverity: "error", summary: "An L0 entry declares a wider audience than the member or external export it re-exports" },
|
|
21737
|
+
{ code: "EXPORT_AUDIENCE_NARROW", defaultSeverity: "notice", summary: "An L0 entry is exported to `department`: outside a hosted instance only this project's family sees it, and a path or git consumer is refused" }
|
|
21225
21738
|
],
|
|
21226
21739
|
check(ctx) {
|
|
21227
21740
|
const tables = ctx.exportTables;
|
|
@@ -21254,6 +21767,19 @@ var exportTablesRule = {
|
|
|
21254
21767
|
}
|
|
21255
21768
|
}
|
|
21256
21769
|
}
|
|
21770
|
+
const own2 = tables.find((t) => t.level === "project" && t.owner === ctx.system.name);
|
|
21771
|
+
for (const entry of own2?.entries ?? []) {
|
|
21772
|
+
if ((entry.audience ?? "instance") !== "department") continue;
|
|
21773
|
+
ctx.addIssue(
|
|
21774
|
+
"notice",
|
|
21775
|
+
"EXPORT_AUDIENCE_NARROW",
|
|
21776
|
+
`The project's L0 export table exports "${entry.publicName}" to \`department\`. A path, git or hosted consumer outside this project's family reads it at \`instance\` and is refused it (\`externals add\` says so); only this project's family sees it locally, and, on a hosted instance, the units of the owning department \u2014 which of those its consumers are cannot be told from this tree. Audiences, narrowest first: project < department < instance < partner < external. Widen it to \`instance\` if a sibling checkout or a git consumer uses it, or keep it (and allow this notice) when its consumers are hosted department units.`,
|
|
21777
|
+
own2.owner,
|
|
21778
|
+
false,
|
|
21779
|
+
void 0,
|
|
21780
|
+
{ at: entry.publicName }
|
|
21781
|
+
);
|
|
21782
|
+
}
|
|
21257
21783
|
}
|
|
21258
21784
|
};
|
|
21259
21785
|
|
|
@@ -21278,10 +21804,11 @@ var externalDeclarationsRule = {
|
|
|
21278
21804
|
if (external.sourceKind !== "unresolved") continue;
|
|
21279
21805
|
if (pinned.get(external.alias)?.snapshot && !external.problem?.startsWith("the `use`") && !isMalformed(external.problem)) continue;
|
|
21280
21806
|
reported.add(external.alias);
|
|
21807
|
+
const unpinnedOutside = external.problem?.startsWith("no project of the family answers") && !pinned.get(external.alias)?.snapshot;
|
|
21281
21808
|
ctx.addIssue(
|
|
21282
21809
|
"notice",
|
|
21283
21810
|
"EXTERNAL_UNRESOLVED",
|
|
21284
|
-
`This project's .wai/project.yaml declares the external "${external.alias}" (producer "${external.project}"), which does not resolve: ${external.problem}. Fix the alias or the producer id, add \`source: { path: <the producer's root> }\` to say where it lives, or pin it (\`wairon externals pin\`) so this project's gate has something to judge it against.`
|
|
21811
|
+
unpinnedOutside ? `This project's .wai/project.yaml declares the external "${external.alias}" (producer "${external.project}"), which this project's own gate cannot resolve: no project this root contains answers to "${external.project}", and nothing is pinned for it. A parent or sibling is never climbed to from a project's own gate \u2014 its pin is the one thing the gate judges it against. Pin it here (\`wairon externals pin ${external.alias}\`), or give it \`source: { path: <the producer's root> }\`.` : `This project's .wai/project.yaml declares the external "${external.alias}" (producer "${external.project}"), which does not resolve: ${external.problem}. Fix the alias or the producer id, add \`source: { path: <the producer's root> }\` to say where it lives, or pin it (\`wairon externals pin\`) so this project's gate has something to judge it against.`
|
|
21285
21812
|
);
|
|
21286
21813
|
}
|
|
21287
21814
|
for (const problem of family.problems) {
|
|
@@ -21477,6 +22004,50 @@ function label(node, namespace) {
|
|
|
21477
22004
|
const id = node.id ?? node.name ?? "(no id)";
|
|
21478
22005
|
return node.namespace === "" ? `"${id}"` : `"${id}" (keyed "${node.namespace}")`;
|
|
21479
22006
|
}
|
|
22007
|
+
function lastSegment3(id) {
|
|
22008
|
+
return id.split("::").pop().split(".").pop();
|
|
22009
|
+
}
|
|
22010
|
+
function distance2(a, b) {
|
|
22011
|
+
const row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
22012
|
+
for (let i = 1; i <= a.length; i++) {
|
|
22013
|
+
let prev = row[0];
|
|
22014
|
+
row[0] = i;
|
|
22015
|
+
for (let j = 1; j <= b.length; j++) {
|
|
22016
|
+
const next = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
22017
|
+
prev = row[j];
|
|
22018
|
+
row[j] = next;
|
|
22019
|
+
}
|
|
22020
|
+
}
|
|
22021
|
+
return row[b.length];
|
|
22022
|
+
}
|
|
22023
|
+
function didYouMean(ctx, ref) {
|
|
22024
|
+
const written = lastSegment3(ref.authored);
|
|
22025
|
+
const key2 = nameKey(written);
|
|
22026
|
+
let names = [];
|
|
22027
|
+
let renamedTo;
|
|
22028
|
+
const table = ref.producer !== void 0 ? (ctx.exportTables ?? []).find((t) => t.level === "project" && t.owner === ref.producer) : void 0;
|
|
22029
|
+
if (table) {
|
|
22030
|
+
names = table.entries.map((e) => e.publicName);
|
|
22031
|
+
const traced2 = (holder) => (holder?.previousIds ?? []).some((p) => nameKey(lastSegment3(p)) === key2);
|
|
22032
|
+
for (const e of table.entries) {
|
|
22033
|
+
const holder = e.kind === "type" ? ctx.types.find((t) => t.id === e.typeDef || t.id === `${ref.producer}::${e.typeDef}`) : e.interface !== void 0 ? ctx.interfaceMap.get(e.interface) : ctx.componentMap.get(e.component ?? "");
|
|
22034
|
+
if (traced2(holder)) {
|
|
22035
|
+
renamedTo = e.publicName;
|
|
22036
|
+
break;
|
|
22037
|
+
}
|
|
22038
|
+
}
|
|
22039
|
+
} else {
|
|
22040
|
+
const alias = ref.authored.includes("::") ? ref.authored.split("::")[0] : void 0;
|
|
22041
|
+
const pin2 = alias !== void 0 ? ctx.pinnedExternals.find((p) => p.alias === alias)?.snapshot : void 0;
|
|
22042
|
+
if (pin2) {
|
|
22043
|
+
names = [...pin2.interfaces.map((e) => e.id), ...(pin2.exportedTypes ?? []).map((t) => t.id)];
|
|
22044
|
+
renamedTo = pin2.interfaces.find((e) => (e.formerly ?? []).some((f) => nameKey(f) === key2))?.id ?? (pin2.exportedTypes ?? []).find((t) => (pin2.types.find((d) => d.id === t.type)?.formerly ?? []).some((f) => nameKey(f) === key2))?.id ?? pin2.unresolvedExports?.find((u) => nameKey(u.id) === key2)?.renamedTo;
|
|
22045
|
+
}
|
|
22046
|
+
}
|
|
22047
|
+
if (renamedTo !== void 0 && renamedTo !== written) return ` It was renamed to "${renamedTo}" (the producer's rename trace records "${written}") \u2014 did you mean "${renamedTo}"?`;
|
|
22048
|
+
const close2 = [...new Set(names)].filter((n) => n !== written).sort((a, b) => distance2(written, a) - distance2(written, b) || (a < b ? -1 : 1)).filter((n) => distance2(written, n) <= Math.max(2, Math.floor(written.length / 2))).slice(0, 3);
|
|
22049
|
+
return close2.length ? ` Did you mean ${close2.map((n) => `"${n}"`).join(" or ")}?` : "";
|
|
22050
|
+
}
|
|
21480
22051
|
function declaration(producer) {
|
|
21481
22052
|
const id = producer?.id;
|
|
21482
22053
|
if (!id || !PROJECT_ID_RE.test(id)) return "an `externals` entry naming it (it has no usable id yet \u2014 declare one first)";
|
|
@@ -21517,7 +22088,7 @@ var projectBoundariesRule = {
|
|
|
21517
22088
|
ctx.addIssue(
|
|
21518
22089
|
"error",
|
|
21519
22090
|
"EXTERNAL_NOT_EXPORTED",
|
|
21520
|
-
`${where}, which reaches "${resolution.canonicalTarget}" at no public name the referrer may see: ${resolution.reason}
|
|
22091
|
+
`${where}, which reaches "${resolution.canonicalTarget}" at no public name the referrer may see: ${resolution.reason}.${didYouMean(ctx, ref)} Another project may reach it only through a public name of its L0 table whose audience covers the referrer \u2014 export it from that project's L0 (a project-level type takes one own \`{ typeDef }\` entry) and re-pin, or reach it by a name the producer exports.`,
|
|
21521
22092
|
ref.specId,
|
|
21522
22093
|
draft,
|
|
21523
22094
|
resolution
|
|
@@ -22553,10 +23124,10 @@ var portalFieldsRule = {
|
|
|
22553
23124
|
name: "portal-fields",
|
|
22554
23125
|
judges: "design",
|
|
22555
23126
|
scope: "spec",
|
|
22556
|
-
description: "A Portal declares its transport (the retired portalType is read as it for one release); only an InProcess Portal may declare an abi.
|
|
23127
|
+
description: "A Portal declares its transport (the retired portalType is read as it for one release); only an InProcess Portal may declare an abi. An Adapter may declare a transport: the one it calls its target Portal over (adapter-transport judges it against the target). Every other non-Portal component carries no transport, and no non-Portal carries an abi, basePath, auth or Portal-level invokedBy entry (auth is inbound transport auth: it belongs on the Portal that exposes the surface; an entry on a non-Portal is entry-declarations' ENTRY_ON_NON_PORTAL). Intrinsic to one component: no tree required.",
|
|
22557
23128
|
codes: [
|
|
22558
23129
|
{ code: "MISSING_PORTAL_TRANSPORT", defaultSeverity: "error", summary: "Portal without a transport" },
|
|
22559
|
-
{ code: "UNEXPECTED_PORTAL_FIELD", defaultSeverity: "error", summary: "Non-Portal component with
|
|
23130
|
+
{ code: "UNEXPECTED_PORTAL_FIELD", defaultSeverity: "error", summary: "Non-Portal component with an abi or basePath, or a transport on one that is not an Adapter, or an abi on a Portal whose transport is not InProcess" },
|
|
22560
23131
|
{ code: "AUTH_ON_NON_PORTAL", defaultSeverity: "warning", summary: "Non-Portal component declaring auth (auth is inbound transport auth, only meaningful on a Portal)" }
|
|
22561
23132
|
],
|
|
22562
23133
|
check(ctx) {
|
|
@@ -22583,11 +23154,12 @@ var portalFieldsRule = {
|
|
|
22583
23154
|
}
|
|
22584
23155
|
continue;
|
|
22585
23156
|
}
|
|
22586
|
-
|
|
23157
|
+
const strayTransport = comp.transport !== void 0 && comp.componentType !== "Adapter";
|
|
23158
|
+
if (strayTransport || comp.abi !== void 0 || comp.basePath !== void 0) {
|
|
22587
23159
|
ctx.addIssue(
|
|
22588
23160
|
"error",
|
|
22589
23161
|
"UNEXPECTED_PORTAL_FIELD",
|
|
22590
|
-
`Component "${comp.id}" is a ${comp.componentType}, not a Portal, but has "transport", "abi" or "basePath" configured.
|
|
23162
|
+
`Component "${comp.id}" is a ${comp.componentType}, not a Portal, but has "transport", "abi" or "basePath" configured. abi and basePath are Portal-only, and a transport belongs to a Portal (or to an Adapter, as the one it calls its target over) \u2014 drop them, or make this component a Portal.`,
|
|
22591
23163
|
comp.id,
|
|
22592
23164
|
isDraftCtx
|
|
22593
23165
|
);
|
|
@@ -24531,6 +25103,48 @@ var networkBoundariesRule = {
|
|
|
24531
25103
|
}
|
|
24532
25104
|
};
|
|
24533
25105
|
|
|
25106
|
+
// src/core/rules/wiring/adapter-transport.ts
|
|
25107
|
+
var adapterTransportRule = {
|
|
25108
|
+
name: "adapter-transport",
|
|
25109
|
+
judges: "design",
|
|
25110
|
+
description: "An Adapter calls a verb on its target Portal's interface, so its transport is the target's: inferred from the target Portal when the Adapter states none (HTTP for an HTTP API, a database connection for a database Portal, ...). An Adapter MAY state its own transport; when it does and a Portal it calls (one it names in dependsOn, or the target of one of its call, register or dispatch steps or declared calls) has another transport, that is ADAPTER_TRANSPORT_MISMATCH, one finding per target \u2014 so changing a Portal's transport shows its impact on every Adapter that calls it. A Portal the tree cannot resolve (an external one) is not judged here. Reads the components and narratives of the tree.",
|
|
25111
|
+
codes: [
|
|
25112
|
+
{ code: "ADAPTER_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "An Adapter states a transport other than the transport of a Portal it calls" }
|
|
25113
|
+
],
|
|
25114
|
+
check(ctx) {
|
|
25115
|
+
for (const adapter of ctx.components.filter((c) => c.componentType === "Adapter" && c.transport !== void 0)) {
|
|
25116
|
+
const targets = /* @__PURE__ */ new Map();
|
|
25117
|
+
const consider = (id) => {
|
|
25118
|
+
if (!id) return;
|
|
25119
|
+
const target = ctx.componentMap.get(id);
|
|
25120
|
+
if (target?.componentType === "Portal" && target.transport !== void 0) targets.set(target.id, target);
|
|
25121
|
+
};
|
|
25122
|
+
for (const dep of adapter.dependsOn ?? []) consider(dep);
|
|
25123
|
+
for (const impl of ctx.implementations) {
|
|
25124
|
+
if (ctx.interfaceMap.get(impl.contract)?.component !== adapter.id) continue;
|
|
25125
|
+
for (const method of impl.methods) {
|
|
25126
|
+
for (const step of method.narrative) {
|
|
25127
|
+
if (step.type === "call" || step.type === "register" || step.type === "dispatch") consider(step.targetComponent);
|
|
25128
|
+
}
|
|
25129
|
+
for (const entry of method.calls ?? []) consider(parseDeclaredCall(entry)?.compId);
|
|
25130
|
+
}
|
|
25131
|
+
}
|
|
25132
|
+
for (const target of [...targets.values()].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
25133
|
+
if (target.transport === adapter.transport) continue;
|
|
25134
|
+
ctx.addIssue(
|
|
25135
|
+
"error",
|
|
25136
|
+
"ADAPTER_TRANSPORT_MISMATCH",
|
|
25137
|
+
`Adapter "${adapter.id}" states transport "${adapter.transport}", but it calls Portal "${target.id}", whose transport is "${target.transport}": an Adapter calls its target's verbs over the target's transport. Follow the Portal's transport (set "${target.transport}"), or drop the Adapter's transport to take its target's.`,
|
|
25138
|
+
adapter.id,
|
|
25139
|
+
ctx.isComponentDraft(adapter.id),
|
|
25140
|
+
void 0,
|
|
25141
|
+
{ at: target.id }
|
|
25142
|
+
);
|
|
25143
|
+
}
|
|
25144
|
+
}
|
|
25145
|
+
}
|
|
25146
|
+
};
|
|
25147
|
+
|
|
24534
25148
|
// src/core/rules/narrative-graph-projector.ts
|
|
24535
25149
|
var methodKey = (compId, methodName) => `${compId}#${methodName}`;
|
|
24536
25150
|
function stepEdges(step, ctx, followRegisterEdges) {
|
|
@@ -24729,6 +25343,10 @@ function portalScopeHint(comp) {
|
|
|
24729
25343
|
}
|
|
24730
25344
|
|
|
24731
25345
|
// src/core/rules/wiring/invoked-by-description.ts
|
|
25346
|
+
function floorNote(caller) {
|
|
25347
|
+
const length = (caller ?? "").trim().length;
|
|
25348
|
+
return ` (the floor: at least 40 characters once trimmed, and more than the method's own name \u2014 this caller has ${length})`;
|
|
25349
|
+
}
|
|
24732
25350
|
var invokedByDescriptionRule = {
|
|
24733
25351
|
name: "invoked-by-description",
|
|
24734
25352
|
judges: "design",
|
|
@@ -24747,7 +25365,7 @@ var invokedByDescriptionRule = {
|
|
|
24747
25365
|
ctx.addIssue(
|
|
24748
25366
|
"warning",
|
|
24749
25367
|
"INVOKED_BY_UNDESCRIBED",
|
|
24750
|
-
`Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
|
|
25368
|
+
`Method "${m.name}" on component "${intf.component}" declares invokedBy (${m.invokedBy.kind}) but its "caller" prose is missing or placeholder-thin${floorNote(m.invokedBy.caller)} \u2014 state WHO invokes it and when, so the entrypoint claim stays reviewable.`,
|
|
24751
25369
|
intf.id,
|
|
24752
25370
|
isDraftCtx
|
|
24753
25371
|
);
|
|
@@ -24759,7 +25377,7 @@ var invokedByDescriptionRule = {
|
|
|
24759
25377
|
ctx.addIssue(
|
|
24760
25378
|
"warning",
|
|
24761
25379
|
"INVOKED_BY_UNDESCRIBED",
|
|
24762
|
-
`Portal "${comp.id}" declares invokedBy (${comp.invokedBy.kind}) for every verb, but its "caller" prose is missing or placeholder-thin \u2014 state WHO reaches the Portal and how, so the entry claim stays reviewable.`,
|
|
25380
|
+
`Portal "${comp.id}" declares invokedBy (${comp.invokedBy.kind}) for every verb, but its "caller" prose is missing or placeholder-thin${floorNote(comp.invokedBy.caller)} \u2014 state WHO reaches the Portal and how, so the entry claim stays reviewable.`,
|
|
24763
25381
|
comp.id,
|
|
24764
25382
|
ctx.isComponentDraft(comp.id)
|
|
24765
25383
|
);
|
|
@@ -25314,7 +25932,7 @@ var sourceFileLinkageRule = {
|
|
|
25314
25932
|
ctx.addIssue(
|
|
25315
25933
|
"warning",
|
|
25316
25934
|
"CONFORMANCE_DEGRADED",
|
|
25317
|
-
`${degradedTsFiles.length} TypeScript/JavaScript source file(s) were analyzed below exact grade \u2014
|
|
25935
|
+
`${degradedTsFiles.length} TypeScript/JavaScript source file(s) were analyzed below exact grade \u2014 no TypeScript compiler API could be loaded from the analyzed project or the wairon installation: either no \`typescript\` package is installed there, or the installed one ships no JavaScript compiler API (TypeScript 7's native compiler has none; this analyzer reads the TypeScript 5.x API). Structural findings carry their grade, but dependency conformance skips these files. To restore exact analysis, make a TypeScript 5.x package resolvable from the analyzed project (\`npm i -D typescript@5\`) \u2014 on TypeScript 7, check \`node -e "console.log(typeof require('typescript').createSourceFile)"\` prints function.`
|
|
25318
25936
|
);
|
|
25319
25937
|
}
|
|
25320
25938
|
for (const impl of ctx.implementations) {
|
|
@@ -25535,6 +26153,9 @@ function closedCallSites(code, file, fn, stopAt, container) {
|
|
|
25535
26153
|
}
|
|
25536
26154
|
return out;
|
|
25537
26155
|
}
|
|
26156
|
+
function isRemoteLink(caller, target) {
|
|
26157
|
+
return caller.componentType === "Adapter" && target.componentType === "Portal" && target.transport !== void 0 && target.transport !== "InProcess";
|
|
26158
|
+
}
|
|
25538
26159
|
var anchorKey = (symbol, exportedVia) => exportedVia !== void 0 ? `${exportedVia}.${symbol}` : symbol;
|
|
25539
26160
|
var bodyKey = (file, symbol, exportedVia) => `${file}|${anchorKey(symbol, exportedVia)}`;
|
|
25540
26161
|
function resolveCallTarget(ctx, componentId, methodName) {
|
|
@@ -25665,6 +26286,7 @@ var callConformanceRule = {
|
|
|
25665
26286
|
const missed = [];
|
|
25666
26287
|
for (const claim of claimsOf(implMethod)) {
|
|
25667
26288
|
if (!ctx.componentMap.has(claim.component)) continue;
|
|
26289
|
+
if (isRemoteLink(component, ctx.componentMap.get(claim.component))) continue;
|
|
25668
26290
|
const target = resolveCallTarget(ctx, claim.component, claim.method);
|
|
25669
26291
|
if (target.bodies.has(bodyKey(here, fnSymbol, implMethod.exportedVia))) continue;
|
|
25670
26292
|
const sharedBody = targetForwards(code, target);
|
|
@@ -25822,9 +26444,11 @@ var hiddenStateRule = {
|
|
|
25822
26444
|
var dependencyConformanceRule = {
|
|
25823
26445
|
name: "dependency-conformance",
|
|
25824
26446
|
judges: "code",
|
|
25825
|
-
description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning)
|
|
26447
|
+
description: "Code\u2194spec Level 2: runtime import edges between component-mapped source files (a component maps to every file its implementations and their methods name) must be justified by declared relations \u2014 a direct dependsOn/owns pair, a shared component, membership in a depended-on pattern, or (across subsystems) a declared edge to the target subsystem's published surface (UNDECLARED_DEPENDENCY). A type-only import alone never accuses \u2014 type coupling is allowed \u2014 but a CALL of a modelled contract method through a collaborator whose declared type comes from another component's file is runtime collaboration whatever the import's form, and the same justification is owed (UNDECLARED_DEPENDENCY on that edge). Conversely, a declared dependsOn/owns edge between components realized in different files should be visible as an import between any file of the source and any file of the target (UNREALIZED_DEPENDENCY \u2014 DI indirection can defeat this, hence warning); a type-only import realizes it, since `import type` is what dependency injection writes down. Two twins of design rules are judged on the same code: a Portal file calling a write- or lifecycle-effect contract method of a Repository, Index, Store or Registry is the persistence shortcut the design rule PORTAL_WRITE_SHORTCUT refuses, unless the Portal's own narrative already claims that call (the design rule's finding then) (PORTAL_WRITE_SHORTCUT_IN_CODE); and a file importing a technology's package \u2014 a bare specifier whose package name is one of the technology's declared tokens (its name, or its `matches` when it declares them), compared exactly, never guessed \u2014 while no component it realizes is that technology's home is the leak TECH_LEAKAGE refuses in the design (TECH_LEAKAGE_IN_CODE). Only exact-grade analyzed files participate; chained subprojects validate standalone.",
|
|
25826
26448
|
codes: [
|
|
25827
|
-
{ code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
|
|
26449
|
+
{ code: "UNDECLARED_DEPENDENCY", defaultSeverity: "warning", summary: "A runtime import, or a call through a type-only-imported collaborator, between component-mapped files has no declared dependsOn/owns (or published-surface) justification" },
|
|
26450
|
+
{ code: "PORTAL_WRITE_SHORTCUT_IN_CODE", defaultSeverity: "error", summary: "A Portal's code calls a write- or lifecycle-effect method of a data component (Repository, Index, Store, Registry) directly \u2014 the code twin of PORTAL_WRITE_SHORTCUT: mutations route through an Orchestrator" },
|
|
26451
|
+
{ code: "TECH_LEAKAGE_IN_CODE", defaultSeverity: "warning", summary: "A source file imports a technology's package although no component it realizes binds that technology \u2014 the code twin of TECH_LEAKAGE" },
|
|
25828
26452
|
{ code: "UNREALIZED_DEPENDENCY", defaultSeverity: "warning", summary: "A declared dependsOn/owns edge between components in different files is realized by no import between any of their files" }
|
|
25829
26453
|
],
|
|
25830
26454
|
check(ctx) {
|
|
@@ -25940,6 +26564,149 @@ var dependencyConformanceRule = {
|
|
|
25940
26564
|
);
|
|
25941
26565
|
}
|
|
25942
26566
|
}
|
|
26567
|
+
const DATA_COMPONENTS2 = /* @__PURE__ */ new Set(["Repository", "Index", "Store", "Registry"]);
|
|
26568
|
+
const calledMethodOf = (componentId, name) => {
|
|
26569
|
+
const methods = ctx.interfaceMethodsOf(componentId);
|
|
26570
|
+
const direct = methods.find((m) => m.name === name);
|
|
26571
|
+
if (direct) return { name: direct.name, effect: direct.effect };
|
|
26572
|
+
for (const impl of realization.implementationsOf(componentId)) {
|
|
26573
|
+
const bound2 = impl.methods.find((m) => m.symbol === name);
|
|
26574
|
+
if (bound2) {
|
|
26575
|
+
const contract = methods.find((m) => m.name === bound2.name);
|
|
26576
|
+
return { name: bound2.name, effect: contract?.effect };
|
|
26577
|
+
}
|
|
26578
|
+
}
|
|
26579
|
+
return void 0;
|
|
26580
|
+
};
|
|
26581
|
+
const claims = (portalId, target, method) => realization.implementationsOf(portalId).some((impl) => impl.methods.some((m) => m.narrative.some((step) => step.targetComponent === target && step.targetMethod === method) || (m.calls ?? []).includes(`${target}.${method}`)));
|
|
26582
|
+
const typedLandings = (site, file) => {
|
|
26583
|
+
const scope = pathKey(site.from ?? file);
|
|
26584
|
+
const facts = code.factsAt(scope);
|
|
26585
|
+
if (!facts || !site.member) return [];
|
|
26586
|
+
let typeNames = [];
|
|
26587
|
+
if (site.field) typeNames = fieldTypesOf(facts, site.field);
|
|
26588
|
+
else if (site.via && !importBindingOf(facts, site.via)) typeNames = localTypesOf(facts, site.via);
|
|
26589
|
+
const out = [];
|
|
26590
|
+
for (const typeName of typeNames) {
|
|
26591
|
+
const typeOnly = facts.typeOnlyBindings?.[typeName];
|
|
26592
|
+
const runtime = typeOnly === void 0 ? importBindingOf(facts, typeName) : void 0;
|
|
26593
|
+
const specifier = typeOnly ?? (runtime && !runtime.namespace ? runtime.from : void 0);
|
|
26594
|
+
if (!specifier) continue;
|
|
26595
|
+
const toPath = resolveImport(scope, specifier, mappedPaths, code.packages);
|
|
26596
|
+
if (toPath && toPath !== scope) out.push({ toPath, typeOnly: typeOnly !== void 0 });
|
|
26597
|
+
}
|
|
26598
|
+
return out;
|
|
26599
|
+
};
|
|
26600
|
+
const calledAt = (toPath, name) => realization.componentsAt(toPath).map((c) => ({ component: c, method: calledMethodOf(c.id, name) })).filter((c) => c.method !== void 0);
|
|
26601
|
+
const reportedEdges = /* @__PURE__ */ new Set();
|
|
26602
|
+
const reportedWrites = /* @__PURE__ */ new Set();
|
|
26603
|
+
for (const fromPath of mappedPaths) {
|
|
26604
|
+
const facts = code.factsAt(fromPath);
|
|
26605
|
+
const fromComponents = realization.componentsAt(fromPath);
|
|
26606
|
+
if (!facts || fromComponents.length === 0) continue;
|
|
26607
|
+
for (const portal of fromComponents.filter((c) => c.componentType === "Portal")) {
|
|
26608
|
+
for (const impl of realization.implementationsOf(portal.id)) {
|
|
26609
|
+
for (const method of impl.methods) {
|
|
26610
|
+
if (pathKey(methodSourceFile(method, impl.sourcePath) ?? "") !== fromPath) continue;
|
|
26611
|
+
const sites = closedCallSites(code, fromPath, method.symbol ?? method.name, void 0, method.exportedVia) ?? [];
|
|
26612
|
+
for (const site of sites) {
|
|
26613
|
+
if (!site.member || pathKey(site.from ?? fromPath) !== fromPath) continue;
|
|
26614
|
+
const landings = /* @__PURE__ */ new Set([...typedLandings(site, fromPath).map((l) => l.toPath), ...code.originOf(site, fromPath)]);
|
|
26615
|
+
for (const toPath of landings) {
|
|
26616
|
+
if (toPath === fromPath) continue;
|
|
26617
|
+
for (const { component: target, method: called } of calledAt(toPath, site.name)) {
|
|
26618
|
+
if (!DATA_COMPONENTS2.has(target.componentType)) continue;
|
|
26619
|
+
if (called.effect !== "write" && called.effect !== "lifecycle") continue;
|
|
26620
|
+
if (claims(portal.id, target.id, called.name)) continue;
|
|
26621
|
+
const at = `${fromPath} -> ${target.id}.${called.name}`;
|
|
26622
|
+
if (reportedWrites.has(at)) continue;
|
|
26623
|
+
reportedWrites.add(at);
|
|
26624
|
+
ctx.addIssue(
|
|
26625
|
+
"error",
|
|
26626
|
+
"PORTAL_WRITE_SHORTCUT_IN_CODE",
|
|
26627
|
+
`Portal "${portal.id}": its method "${method.name}" in "${fromPath}" calls ${called.effect}-effect method ${target.id}.${called.name} directly (\`${site.via ?? (site.field ? `this.${site.field}` : "<receiver>")}.${site.name}(\u2026)\`), which its design never narrates \u2014 the code twin of PORTAL_WRITE_SHORTCUT. A Portal may reach a data component for READS only: route the ${called.effect === "write" ? "write" : "lifecycle change"} through an Orchestrator that owns the workflow.`,
|
|
26628
|
+
impl.id,
|
|
26629
|
+
draftAt(fromPath) || draftAt(toPath),
|
|
26630
|
+
void 0,
|
|
26631
|
+
{ at }
|
|
26632
|
+
);
|
|
26633
|
+
}
|
|
26634
|
+
}
|
|
26635
|
+
}
|
|
26636
|
+
}
|
|
26637
|
+
}
|
|
26638
|
+
}
|
|
26639
|
+
const runtime = graph2.importsOf(fromPath);
|
|
26640
|
+
for (const sites of Object.values(facts.functionCallSites ?? {})) {
|
|
26641
|
+
for (const site of sites) {
|
|
26642
|
+
for (const { toPath, typeOnly } of typedLandings(site, fromPath)) {
|
|
26643
|
+
if (!typeOnly || runtime.has(toPath)) continue;
|
|
26644
|
+
const called = calledAt(toPath, site.name);
|
|
26645
|
+
if (called.length === 0) continue;
|
|
26646
|
+
const toComponents = realization.componentsAt(toPath);
|
|
26647
|
+
if (edgeJustified(fromComponents, toComponents)) continue;
|
|
26648
|
+
const edge = `${fromPath} -> ${toPath}`;
|
|
26649
|
+
if (reportedEdges.has(edge)) continue;
|
|
26650
|
+
reportedEdges.add(edge);
|
|
26651
|
+
ctx.addIssue(
|
|
26652
|
+
"warning",
|
|
26653
|
+
"UNDECLARED_DEPENDENCY",
|
|
26654
|
+
`"${fromPath}" (realizing ${fromComponents.map((c) => c.id).join(", ")}) calls ${called.map((c) => `${c.component.id}.${c.method.name}`).join(", ")} through a collaborator typed by a type-only import of "${toPath}" (realizing ${toComponents.map((c) => c.id).join(", ")}), but no declared dependsOn/owns edge justifies it \u2014 a call through an injected collaborator is runtime collaboration whatever the import's form. Declare the collaboration on the component that uses it, or route the cross-subsystem hop through the target's published surface.`,
|
|
26655
|
+
realization.implementationsAt(fromPath)[0]?.id,
|
|
26656
|
+
draftAt(fromPath) || draftAt(toPath),
|
|
26657
|
+
void 0,
|
|
26658
|
+
{ at: edge }
|
|
26659
|
+
);
|
|
26660
|
+
}
|
|
26661
|
+
}
|
|
26662
|
+
}
|
|
26663
|
+
}
|
|
26664
|
+
const homes = /* @__PURE__ */ new Map();
|
|
26665
|
+
for (const impl of ctx.implementations) {
|
|
26666
|
+
const technologies = impl.technologies ?? [];
|
|
26667
|
+
if (technologies.length === 0) continue;
|
|
26668
|
+
const contract = ctx.interfaceMap.get(impl.contract);
|
|
26669
|
+
const owner = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
26670
|
+
if (!owner) continue;
|
|
26671
|
+
const members = /* @__PURE__ */ new Set([owner.id, ...owner.owns ?? []]);
|
|
26672
|
+
const facade = ownerOf4.get(owner.id);
|
|
26673
|
+
if (facade) members.add(facade.id);
|
|
26674
|
+
for (const technology of technologies) {
|
|
26675
|
+
for (const token of technologyTokens(technology)) {
|
|
26676
|
+
const key2 = token.toLowerCase();
|
|
26677
|
+
const entry = homes.get(key2) ?? { label: technologyName(technology), home: /* @__PURE__ */ new Set() };
|
|
26678
|
+
for (const id of members) entry.home.add(id);
|
|
26679
|
+
homes.set(key2, entry);
|
|
26680
|
+
}
|
|
26681
|
+
}
|
|
26682
|
+
}
|
|
26683
|
+
if (homes.size > 0) {
|
|
26684
|
+
for (const file of mappedPaths) {
|
|
26685
|
+
const facts = code.factsAt(file);
|
|
26686
|
+
if (!facts) continue;
|
|
26687
|
+
const atFile = realization.componentsAt(file);
|
|
26688
|
+
const specifiers = /* @__PURE__ */ new Set([...facts.imports, ...Object.values(facts.typeOnlyBindings ?? {})]);
|
|
26689
|
+
const reported = /* @__PURE__ */ new Set();
|
|
26690
|
+
for (const specifier of specifiers) {
|
|
26691
|
+
if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("node:")) continue;
|
|
26692
|
+
if (resolveImport(file, specifier, code.paths, code.packages)) continue;
|
|
26693
|
+
const parts = specifier.split("/");
|
|
26694
|
+
const pkg = (specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0]).toLowerCase();
|
|
26695
|
+
const tech = homes.get(pkg);
|
|
26696
|
+
if (!tech || reported.has(pkg) || atFile.some((c) => tech.home.has(c.id))) continue;
|
|
26697
|
+
reported.add(pkg);
|
|
26698
|
+
ctx.addIssue(
|
|
26699
|
+
"warning",
|
|
26700
|
+
"TECH_LEAKAGE_IN_CODE",
|
|
26701
|
+
`"${file}" (realizing ${atFile.map((c) => c.id).join(", ")}) imports "${specifier}", the package of technology "${tech.label}", whose home is ${[...tech.home].join(", ")} \u2014 the code twin of TECH_LEAKAGE. Move the vendor call behind the component that binds it, or bind the technology where it is really used.`,
|
|
26702
|
+
realization.implementationsAt(file)[0]?.id,
|
|
26703
|
+
draftAt(file),
|
|
26704
|
+
void 0,
|
|
26705
|
+
{ at: `${file} -> ${pkg}` }
|
|
26706
|
+
);
|
|
26707
|
+
}
|
|
26708
|
+
}
|
|
26709
|
+
}
|
|
25943
26710
|
}
|
|
25944
26711
|
};
|
|
25945
26712
|
|
|
@@ -26613,16 +27380,16 @@ var paramConformanceRule = {
|
|
|
26613
27380
|
{ at: method.name, covers: undeclared.map((found) => found.unit) }
|
|
26614
27381
|
);
|
|
26615
27382
|
}
|
|
26616
|
-
const
|
|
26617
|
-
if (
|
|
27383
|
+
const renamed2 = agreed(judgements, "renamed");
|
|
27384
|
+
if (renamed2.length > 0) {
|
|
26618
27385
|
ctx.addIssue(
|
|
26619
27386
|
"warning",
|
|
26620
27387
|
"PARAM_NAME_MISMATCH",
|
|
26621
|
-
`Method "${method.name}" of contract "${implementation.contract}" and ${subject} agree on position and type but not on name for ${
|
|
27388
|
+
`Method "${method.name}" of contract "${implementation.contract}" and ${subject} agree on position and type but not on name for ${renamed2.length} parameter(s) \u2014 ${renamed2.map((f) => f.told).join("; ")}. The contract, the ERD and every brief carry one word and the code answers to another, which is a rename nobody recorded rather than a different argument \u2014 the declared type agreeing is what says so. Rename one side to the other.`,
|
|
26622
27389
|
implementation.id,
|
|
26623
27390
|
draftContext4,
|
|
26624
27391
|
void 0,
|
|
26625
|
-
{ at: method.name, covers:
|
|
27392
|
+
{ at: method.name, covers: renamed2.map((found) => found.unit) }
|
|
26626
27393
|
);
|
|
26627
27394
|
}
|
|
26628
27395
|
const disagreed = agreed(judgements, "optionality");
|
|
@@ -28348,6 +29115,7 @@ var SDD_RULES = [
|
|
|
28348
29115
|
invokedByDescriptionRule,
|
|
28349
29116
|
reachabilityRule,
|
|
28350
29117
|
networkBoundariesRule,
|
|
29118
|
+
adapterTransportRule,
|
|
28351
29119
|
unusedTypesRule,
|
|
28352
29120
|
// The declaration (spec-scoped, refused at the write boundary) before the
|
|
28353
29121
|
// round-trip consequences it enables.
|
|
@@ -28697,7 +29465,9 @@ function resolveTypeScript(projectRoot) {
|
|
|
28697
29465
|
for (const base of bases) {
|
|
28698
29466
|
try {
|
|
28699
29467
|
const req = (0, import_module2.createRequire)(base);
|
|
28700
|
-
|
|
29468
|
+
const candidate = req("typescript");
|
|
29469
|
+
if (typeof candidate?.createSourceFile !== "function") continue;
|
|
29470
|
+
ts = candidate;
|
|
28701
29471
|
break;
|
|
28702
29472
|
} catch {
|
|
28703
29473
|
}
|
|
@@ -30703,19 +31473,19 @@ function measure(candidate) {
|
|
|
30703
31473
|
const config = loadProjectConfig();
|
|
30704
31474
|
if (!config) throw new Error("Not inside a wairon project: a pack impact is measured on a project \u2014 run `wairon init` first.");
|
|
30705
31475
|
const entry = candidate.entry;
|
|
30706
|
-
const
|
|
30707
|
-
let next = entry !== void 0 ? withPack(config, entry,
|
|
31476
|
+
const remove3 = candidate.remove === true;
|
|
31477
|
+
let next = entry !== void 0 ? withPack(config, entry, remove3) : config;
|
|
30708
31478
|
if (candidate.projectType !== void 0) next = { ...next, projectType: candidate.projectType };
|
|
30709
31479
|
const current2 = entry !== void 0 ? (config.extensions?.packs ?? []).find((e) => sameEntry(e, entry)) : void 0;
|
|
30710
31480
|
let manifest = null;
|
|
30711
|
-
if (candidate.manifest && !
|
|
31481
|
+
if (candidate.manifest && !remove3 || entry === void 0) {
|
|
30712
31482
|
manifest = candidate.manifest ?? null;
|
|
30713
31483
|
} else {
|
|
30714
|
-
const measured =
|
|
31484
|
+
const measured = remove3 ? current2 ?? entry : entry;
|
|
30715
31485
|
manifest = manifestOf(measured);
|
|
30716
31486
|
if (!manifest) throw new Error(`Cannot measure the pack: ${unresolvedReason(config, measured)}`);
|
|
30717
31487
|
}
|
|
30718
|
-
const previous = !
|
|
31488
|
+
const previous = !remove3 && current2 !== void 0 ? manifestOf(current2) : null;
|
|
30719
31489
|
const baseline = defaultsBaseline();
|
|
30720
31490
|
const doctrine2 = manifest ? doctrineChanges(manifest, baseline) : [];
|
|
30721
31491
|
const previousDoctrine = previous ? doctrineChanges(previous, baseline) : void 0;
|
|
@@ -30731,11 +31501,11 @@ function measure(candidate) {
|
|
|
30731
31501
|
return {
|
|
30732
31502
|
pack: manifest?.name ?? (entry !== void 0 ? entryName(entry) : `projectType ${next.projectType}`),
|
|
30733
31503
|
...manifest?.version ? { version: manifest.version } : {},
|
|
30734
|
-
...!
|
|
30735
|
-
direction:
|
|
31504
|
+
...!remove3 && current2 !== void 0 && replaces ? { replaces } : {},
|
|
31505
|
+
direction: remove3 ? "remove" : "apply",
|
|
30736
31506
|
doctrine: doctrine2,
|
|
30737
|
-
...previousDoctrine && !
|
|
30738
|
-
governing: governingProfiles(manifest,
|
|
31507
|
+
...previousDoctrine && !remove3 ? { previousDoctrine } : {},
|
|
31508
|
+
governing: governingProfiles(manifest, remove3 ? config : next),
|
|
30739
31509
|
findings: findingChanges(before, after),
|
|
30740
31510
|
before: totals(config, before),
|
|
30741
31511
|
after: totals(next, after)
|
|
@@ -30749,9 +31519,11 @@ function doctrine(manifest) {
|
|
|
30749
31519
|
|
|
30750
31520
|
// src/core/rules/gate-identity.ts
|
|
30751
31521
|
var crypto4 = __toESM(require("crypto"));
|
|
30752
|
-
var GATE_ALGORITHM = "sha256+design+doctrine+inputs+members";
|
|
31522
|
+
var GATE_ALGORITHM = "sha256+design-2+doctrine+inputs+members";
|
|
31523
|
+
var EARLIER_DESIGN_GATE_ALGORITHM = "sha256+design+doctrine+inputs+members";
|
|
30753
31524
|
var CONTENT_GATE_ALGORITHM = "sha256+content+doctrine+inputs+members";
|
|
30754
|
-
var DESIGN_CONTENT_ALGORITHM = "sha256-design";
|
|
31525
|
+
var DESIGN_CONTENT_ALGORITHM = "sha256-design-2";
|
|
31526
|
+
var EARLIER_DESIGN_CONTENT_ALGORITHM = "sha256-design";
|
|
30755
31527
|
var byKey2 = (items, key2) => [...items].sort((a, b) => compareOrdinal(key2(a), key2(b)));
|
|
30756
31528
|
function ruleIdentities(rules) {
|
|
30757
31529
|
return byKey2(
|
|
@@ -30841,7 +31613,8 @@ function computeGateIdentity(content, doctrine2, builtinRules, inputs, gate, mem
|
|
|
30841
31613
|
members: byKey2(Object.entries(members), ([alias]) => alias).map(([alias, subject]) => ({ alias, subject }))
|
|
30842
31614
|
};
|
|
30843
31615
|
const digest3 = crypto4.createHash("sha256").update(canonicalize(payload)).digest("hex");
|
|
30844
|
-
|
|
31616
|
+
const algorithm = content.algorithm === DESIGN_CONTENT_ALGORITHM ? GATE_ALGORITHM : content.algorithm === EARLIER_DESIGN_CONTENT_ALGORITHM ? EARLIER_DESIGN_GATE_ALGORITHM : CONTENT_GATE_ALGORITHM;
|
|
31617
|
+
return { algorithm, digest: digest3 };
|
|
30845
31618
|
}
|
|
30846
31619
|
function analyzerDigest(builtinRules, gate) {
|
|
30847
31620
|
const codeCodes = codeJudgedCodes(builtinRules);
|
|
@@ -31140,7 +31913,7 @@ function runOwnersGate(rulesOrOptions, projectType = "backend", reachOnly = fals
|
|
|
31140
31913
|
}
|
|
31141
31914
|
}
|
|
31142
31915
|
function ownReachModel(options) {
|
|
31143
|
-
return runOwnersGate(options ?? {}, options?.projectType ?? "backend", true).reach ?? { scope: "own", networks: [], verbs: [], calls: [] };
|
|
31916
|
+
return runOwnersGate(options ?? {}, options?.projectType ?? "backend", true).reach ?? { scope: "own", networks: [], verbs: [], calls: [], placements: [], topics: [] };
|
|
31144
31917
|
}
|
|
31145
31918
|
function reachModel2(options) {
|
|
31146
31919
|
return reachModel(options);
|
|
@@ -31393,7 +32166,16 @@ function computeGateStateId() {
|
|
|
31393
32166
|
const record = approvalRecord(getProjectRoot());
|
|
31394
32167
|
if (!record?.stateId || record.stateId.algorithm === current2.algorithm) return current2;
|
|
31395
32168
|
const asRecorded = computeGateIdentity(computeOwnStateId(), extensions, builtin, inputs, gate, members);
|
|
31396
|
-
|
|
32169
|
+
if (record.stateId.algorithm === asRecorded.algorithm) return { ...current2, asRecorded };
|
|
32170
|
+
const asApproved = computeGateIdentity(
|
|
32171
|
+
computeOwnDesignStateIdAsRecorded(approvedStatuses(record)),
|
|
32172
|
+
extensions,
|
|
32173
|
+
builtin,
|
|
32174
|
+
inputs,
|
|
32175
|
+
gate,
|
|
32176
|
+
members
|
|
32177
|
+
);
|
|
32178
|
+
return record.stateId.algorithm === asApproved.algorithm ? { ...current2, asRecorded: asApproved } : current2;
|
|
31397
32179
|
}
|
|
31398
32180
|
var NEVER_SUBJECT = "never";
|
|
31399
32181
|
function directMemberSubjects() {
|
|
@@ -31732,7 +32514,7 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
31732
32514
|
lines.push("sequenceDiagram");
|
|
31733
32515
|
lines.push(" autonumber");
|
|
31734
32516
|
const declared = /* @__PURE__ */ new Set();
|
|
31735
|
-
const
|
|
32517
|
+
const declare3 = (comp) => {
|
|
31736
32518
|
const pid = ids.idFor(comp.id);
|
|
31737
32519
|
if (!declared.has(pid)) {
|
|
31738
32520
|
declared.add(pid);
|
|
@@ -31740,7 +32522,7 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
31740
32522
|
}
|
|
31741
32523
|
return pid;
|
|
31742
32524
|
};
|
|
31743
|
-
const caller =
|
|
32525
|
+
const caller = declare3(entry);
|
|
31744
32526
|
const walk2 = (comp, method, depth, stack) => {
|
|
31745
32527
|
const key2 = `${comp.id}#${method}`;
|
|
31746
32528
|
if (stack.has(key2)) {
|
|
@@ -31826,12 +32608,12 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
31826
32608
|
lines.push(` Note over ${selfId}: ${escapeLabel(`dispatches via unknown "${step.targetComponent}"`)}`);
|
|
31827
32609
|
break;
|
|
31828
32610
|
}
|
|
31829
|
-
const portalId =
|
|
32611
|
+
const portalId = declare3(portal);
|
|
31830
32612
|
lines.push(step.detach ? ` ${selfId}-)${portalId}: ${escapeLabel(`\u27E8${step.capability ?? "?"}\u27E9`)} \u2014 detached` : ` ${selfId}->>${portalId}: ${escapeLabel(`\u27E8${step.capability ?? "?"}\u27E9`)}`);
|
|
31831
32613
|
const binding = portal.dispatch?.find((b) => b.capability === step.capability);
|
|
31832
32614
|
const server = binding ? componentById.get(binding.component) : void 0;
|
|
31833
32615
|
if (binding && server) {
|
|
31834
|
-
const serverId =
|
|
32616
|
+
const serverId = declare3(server);
|
|
31835
32617
|
const expandable = depth < maxDepth && !!findMethodImpl(server.id, binding.method) && server.id !== comp.id;
|
|
31836
32618
|
if (expandable) {
|
|
31837
32619
|
lines.push(` ${portalId}->>+${serverId}: ${escapeLabel(binding.method)}()`);
|
|
@@ -31850,7 +32632,7 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
31850
32632
|
lines.push(` Note over ${selfId}: ${escapeLabel(`registers callback on unknown "${step.targetComponent}"`)}`);
|
|
31851
32633
|
break;
|
|
31852
32634
|
}
|
|
31853
|
-
const regTargetId =
|
|
32635
|
+
const regTargetId = declare3(regTarget);
|
|
31854
32636
|
lines.push(` ${selfId}--)${regTargetId}: ${escapeLabel(`register ${step.targetMethod}()`)}`);
|
|
31855
32637
|
break;
|
|
31856
32638
|
}
|
|
@@ -31861,7 +32643,7 @@ function generateSequenceDiagram(componentId, methodName, options) {
|
|
|
31861
32643
|
lines.push(` Note over ${selfId}: ${escapeLabel(`calls unknown "${step.targetComponent}"`)}`);
|
|
31862
32644
|
break;
|
|
31863
32645
|
}
|
|
31864
|
-
const targetId =
|
|
32646
|
+
const targetId = declare3(target);
|
|
31865
32647
|
const expandable = depth < maxDepth && !!findMethodImpl(target.id, step.targetMethod) && target.id !== comp.id;
|
|
31866
32648
|
if (step.detach) {
|
|
31867
32649
|
lines.push(` ${selfId}-)${targetId}: ${escapeLabel(step.targetMethod)}() \u2014 detached`);
|
|
@@ -31955,10 +32737,6 @@ function diagramSetIndex(files, systemName) {
|
|
|
31955
32737
|
// src/core/project-family.ts
|
|
31956
32738
|
var path20 = __toESM(require("path"));
|
|
31957
32739
|
var memo = /* @__PURE__ */ new WeakMap();
|
|
31958
|
-
function dirKey(dir) {
|
|
31959
|
-
const resolved = path20.resolve(dir);
|
|
31960
|
-
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
31961
|
-
}
|
|
31962
32740
|
function makeNodes(roots) {
|
|
31963
32741
|
return roots.map((root) => {
|
|
31964
32742
|
const id = root.config ? effectiveProjectId(root.config) : null;
|
|
@@ -32018,18 +32796,9 @@ function identityProblems(nodes) {
|
|
|
32018
32796
|
}
|
|
32019
32797
|
return problems;
|
|
32020
32798
|
}
|
|
32021
|
-
function relationOf(consumer, producer) {
|
|
32022
|
-
if (consumer.parent === producer.namespace) return "parent";
|
|
32023
|
-
if (producer.parent === consumer.namespace) return "member";
|
|
32024
|
-
if (consumer.parent !== void 0 && consumer.parent === producer.parent) return "sibling";
|
|
32025
|
-
return "family";
|
|
32026
|
-
}
|
|
32027
|
-
function familyExternal(alias, project3, consumer, producer) {
|
|
32028
|
-
return { alias, project: project3, sourceKind: "family", relation: relationOf(consumer, producer), producer: producer.namespace, directory: producer.directory, audience: "project", role: "external" };
|
|
32029
|
-
}
|
|
32030
32799
|
function bindExternals(node, root, nodes) {
|
|
32031
32800
|
if (!root.config) return [];
|
|
32032
|
-
return [...declaredExternals(root.config).map((decl) =>
|
|
32801
|
+
return [...declaredExternals(root.config).map((decl) => bindDeclaredExternal(decl, node, nodes)), ...bindReferencedMembers(node, root)];
|
|
32033
32802
|
}
|
|
32034
32803
|
function bindReferencedMembers(node, root) {
|
|
32035
32804
|
const out = [];
|
|
@@ -32054,25 +32823,6 @@ function bindReferencedMembers(node, root) {
|
|
|
32054
32823
|
}
|
|
32055
32824
|
return out;
|
|
32056
32825
|
}
|
|
32057
|
-
function bindExternal(decl, node, nodes) {
|
|
32058
|
-
const unresolved = (problem) => ({ alias: decl.alias, project: decl.project, sourceKind: "unresolved", audience: "instance", problem, role: "external" });
|
|
32059
|
-
if (decl.problem) return unresolved(decl.problem);
|
|
32060
|
-
if (decl.sourceHosted !== void 0) return { alias: decl.alias, project: decl.project, sourceKind: "hosted", hosted: decl.sourceHosted, audience: "instance", role: "external" };
|
|
32061
|
-
if (decl.sourceGit !== void 0) return { alias: decl.alias, project: decl.project, sourceKind: "git", audience: "instance", role: "external" };
|
|
32062
|
-
if (decl.sourcePath !== void 0) {
|
|
32063
|
-
const directory = path20.resolve(node.directory, decl.sourcePath);
|
|
32064
|
-
const found = nodes.find((n) => dirKey(n.directory) === dirKey(directory));
|
|
32065
|
-
if (!found) return { alias: decl.alias, project: decl.project, sourceKind: "path", directory, audience: "instance", role: "external" };
|
|
32066
|
-
if (found.id !== decl.project) {
|
|
32067
|
-
return unresolved(`the family project at source.path "${decl.sourcePath}" answers to ${found.id === void 0 ? "no id" : `"${found.id}"`}, not "${decl.project}"`);
|
|
32068
|
-
}
|
|
32069
|
-
return familyExternal(decl.alias, decl.project, node, found);
|
|
32070
|
-
}
|
|
32071
|
-
const holders = nodes.filter((n) => n.id === decl.project && n !== node);
|
|
32072
|
-
if (holders.length === 0) return unresolved(`no project of the family answers to "${decl.project}", and no source.path says where to find it`);
|
|
32073
|
-
if (holders.length > 1) return unresolved(`${holders.length} projects of the family answer to "${decl.project}" (${holders.map((n) => `"${n.namespace || "(the bound root)"}"`).join(", ")}), so the declaration could mean either`);
|
|
32074
|
-
return familyExternal(decl.alias, decl.project, node, holders[0]);
|
|
32075
|
-
}
|
|
32076
32826
|
function crossReferences(family, specs) {
|
|
32077
32827
|
const out = [];
|
|
32078
32828
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -32194,6 +32944,7 @@ function projectFamilyGraph() {
|
|
|
32194
32944
|
var CONSUMABLE = /* @__PURE__ */ new Set(["Portal", "Observer"]);
|
|
32195
32945
|
var IMPLEMENTABLE = /* @__PURE__ */ new Set(["Portal", "Adapter"]);
|
|
32196
32946
|
function exportKindOf(comp, authored) {
|
|
32947
|
+
if (comp.componentType === "Portal" && comp.transport === "InProcess") return "InProcess";
|
|
32197
32948
|
if (comp.componentType === "Portal" && comp.transport) return transportExportKind(comp.transport);
|
|
32198
32949
|
return authored;
|
|
32199
32950
|
}
|
|
@@ -32241,6 +32992,54 @@ function checkConsumable(entry, owner, problems) {
|
|
|
32241
32992
|
detail: implemented ? `"${entry.publicName}" exports ${entry.componentType} "${entry.component}" for consumers to implement, but an extension point is backed only by a Portal (a webhook or callback contract) or the Adapter holding the port the producer calls` : `"${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 (an Adapter only as an extension point consumers implement, with role implement)`
|
|
32242
32993
|
});
|
|
32243
32994
|
}
|
|
32995
|
+
function renamedInSource(world, table, request) {
|
|
32996
|
+
const wanted = localName2(request.interface ?? request.component ?? request.typeDef ?? "");
|
|
32997
|
+
if (!wanted) return void 0;
|
|
32998
|
+
const traced2 = (holder) => (holder.previousIds ?? []).some((p) => localName2(p) === wanted);
|
|
32999
|
+
for (const entry of table.entries) {
|
|
33000
|
+
if (request.typeDef !== void 0 && entry.kind === "type") {
|
|
33001
|
+
const holders = world.types.get(entry.typeDef ?? "") ?? [];
|
|
33002
|
+
if (holders.some(traced2)) return entry.publicName;
|
|
33003
|
+
}
|
|
33004
|
+
if (request.interface !== void 0 && entry.interface !== void 0) {
|
|
33005
|
+
const intf = world.interfaces.get(entry.interface);
|
|
33006
|
+
if (intf && traced2(intf)) return entry.publicName;
|
|
33007
|
+
}
|
|
33008
|
+
if (request.component !== void 0 && entry.kind === "component" && entry.component !== void 0) {
|
|
33009
|
+
const comp = world.components.get(entry.component);
|
|
33010
|
+
if (comp && traced2(comp)) return entry.publicName;
|
|
33011
|
+
}
|
|
33012
|
+
}
|
|
33013
|
+
return void 0;
|
|
33014
|
+
}
|
|
33015
|
+
function followProducerRenames(table, producerOf2) {
|
|
33016
|
+
for (const problem of table.problems) {
|
|
33017
|
+
if (problem.kind !== "invalid" || problem.source === void 0 || problem.renamedTo !== void 0) continue;
|
|
33018
|
+
const requested = /^re-exports "([^"]+)" from /.exec(problem.detail)?.[1];
|
|
33019
|
+
const producer = requested !== void 0 ? producerOf2(problem.source) : void 0;
|
|
33020
|
+
const renamedTo = producer ? publicRenameTrace(producer.table, producer.specs).get(requested) : void 0;
|
|
33021
|
+
if (renamedTo === void 0) continue;
|
|
33022
|
+
problem.renamedTo = renamedTo;
|
|
33023
|
+
problem.detail = problem.detail.replace(" \u2014 another project is reached only through its L0 exports", ` \u2014 it was renamed to "${renamedTo}" there (did you mean "${renamedTo}"?): re-export the new name`);
|
|
33024
|
+
}
|
|
33025
|
+
}
|
|
33026
|
+
function publicRenameTrace(table, specs) {
|
|
33027
|
+
const trace = /* @__PURE__ */ new Map();
|
|
33028
|
+
const byId = (list) => list.reduce((m, x) => m.set(x.id, [...m.get(x.id) ?? [], x]), /* @__PURE__ */ new Map());
|
|
33029
|
+
const components = byId(specs.components);
|
|
33030
|
+
const interfaces = byId(specs.interfaces);
|
|
33031
|
+
const types = byId(specs.types);
|
|
33032
|
+
for (const entry of table.entries) {
|
|
33033
|
+
const holders = entry.kind === "type" ? types.get(entry.typeDef ?? "") ?? [] : entry.interface !== void 0 ? interfaces.get(entry.interface) ?? [] : components.get(entry.component ?? "") ?? [];
|
|
33034
|
+
for (const holder of holders) {
|
|
33035
|
+
for (const former of holder.previousIds ?? []) {
|
|
33036
|
+
const name = localName2(former);
|
|
33037
|
+
if (name !== entry.publicName && !trace.has(name)) trace.set(name, entry.publicName);
|
|
33038
|
+
}
|
|
33039
|
+
}
|
|
33040
|
+
}
|
|
33041
|
+
return trace;
|
|
33042
|
+
}
|
|
32244
33043
|
function findInSource(world, source, table, request) {
|
|
32245
33044
|
if (request.typeDef) {
|
|
32246
33045
|
const type = lookupType(world, request.typeDef, source.id);
|
|
@@ -32269,7 +33068,15 @@ function bindNamed(world, owner, source, sourceTable, request, publicName, inCyc
|
|
|
32269
33068
|
const { found, item } = findInSource(world, source, sourceTable, request);
|
|
32270
33069
|
const what = request.typeDef ? `type "${request.typeDef}"` : `component "${request.component}"${request.interface ? ` (interface "${request.interface}")` : ""}`;
|
|
32271
33070
|
if (!item) {
|
|
32272
|
-
|
|
33071
|
+
const renamedTo = renamedInSource(world, sourceTable, request);
|
|
33072
|
+
problems.push({
|
|
33073
|
+
kind: "invalid",
|
|
33074
|
+
owner,
|
|
33075
|
+
...request.as !== void 0 ? { publicName: request.as } : {},
|
|
33076
|
+
detail: `re-exports ${what} from "${source.id}", which has no such item${renamedTo ? ` \u2014 it was renamed to "${renamedTo}" there (did you mean "${renamedTo}"?)` : ""}`,
|
|
33077
|
+
...renamedTo ? { renamedTo } : {},
|
|
33078
|
+
source: source.id
|
|
33079
|
+
});
|
|
32273
33080
|
return void 0;
|
|
32274
33081
|
}
|
|
32275
33082
|
const name = publicName(item);
|
|
@@ -32336,7 +33143,7 @@ function subsystemCandidates(world, sub, tables, group, problems) {
|
|
|
32336
33143
|
sub.id,
|
|
32337
33144
|
source,
|
|
32338
33145
|
sourceTable,
|
|
32339
|
-
{ component: pi.component, interface: pi.interface, typeDef: pi.typeDef },
|
|
33146
|
+
{ component: pi.component, interface: pi.interface, typeDef: pi.typeDef, ...pi.as !== void 0 ? { as: pi.as } : {} },
|
|
32340
33147
|
(item) => pi.as ?? localName2(pi.interface ?? item.typeDef ?? item.component ?? ""),
|
|
32341
33148
|
group.has(source.id),
|
|
32342
33149
|
problems
|
|
@@ -32542,7 +33349,14 @@ function bindFromProject(owner, e, source, table, index, problems) {
|
|
|
32542
33349
|
const requested = named2 ? localName2(e.interface ?? e.component ?? e.typeDef) : void 0;
|
|
32543
33350
|
const picked = named2 ? table.entries.filter((x) => x.publicName === requested) : table.entries;
|
|
32544
33351
|
if (named2 && picked.length === 0) {
|
|
32545
|
-
|
|
33352
|
+
const from = e.from ?? e.subsystem;
|
|
33353
|
+
problems.push({
|
|
33354
|
+
kind: "invalid",
|
|
33355
|
+
owner,
|
|
33356
|
+
publicName: e.as ?? e.id ?? requested,
|
|
33357
|
+
detail: `re-exports "${requested}" from ${source.label}, whose export table has no such public name \u2014 another project is reached only through its L0 exports`,
|
|
33358
|
+
...from !== void 0 ? { source: from } : {}
|
|
33359
|
+
});
|
|
32546
33360
|
return [];
|
|
32547
33361
|
}
|
|
32548
33362
|
const declared = e.audience ?? "instance";
|
|
@@ -32572,7 +33386,7 @@ function bindFromSubsystem(world, owner, e, subsystem, subsystemTables, index, p
|
|
|
32572
33386
|
owner,
|
|
32573
33387
|
subsystem,
|
|
32574
33388
|
sourceTable,
|
|
32575
|
-
{ component: e.component, interface: e.interface, typeDef: e.typeDef },
|
|
33389
|
+
{ component: e.component, interface: e.interface, typeDef: e.typeDef, ...(e.as ?? e.id) !== void 0 ? { as: e.as ?? e.id } : {} },
|
|
32576
33390
|
// The default public name is the item's own LOCAL id: a member's L0 is
|
|
32577
33391
|
// read with its ids keyed under its project, and its entry names the
|
|
32578
33392
|
// item as the member wrote it.
|
|
@@ -32735,7 +33549,7 @@ function sameParams2(a, b) {
|
|
|
32735
33549
|
const right = b ?? [];
|
|
32736
33550
|
return left.length === right.length && left.every((p, i) => p.name === right[i].name && p.type === right[i].type && !!p.optional === !!right[i].optional);
|
|
32737
33551
|
}
|
|
32738
|
-
function tablesOf(interfaces, components, types, references) {
|
|
33552
|
+
function tablesOf(interfaces, components, types, references, exportMethods = /* @__PURE__ */ new Map()) {
|
|
32739
33553
|
const methodsOf = /* @__PURE__ */ new Map();
|
|
32740
33554
|
for (const intf of interfaces) methodsOf.set(intf.component, [...methodsOf.get(intf.component) ?? [], ...intf.methods]);
|
|
32741
33555
|
const boundTypes = /* @__PURE__ */ new Map();
|
|
@@ -32743,7 +33557,7 @@ function tablesOf(interfaces, components, types, references) {
|
|
|
32743
33557
|
if (ref.position !== "type" || ref.binding === "outside" || ref.binding === "unresolved") continue;
|
|
32744
33558
|
boundTypes.set(`${ref.specId}|${ref.authored}`, ref.resolved);
|
|
32745
33559
|
}
|
|
32746
|
-
return { methodsOf, componentKeys: new Set(components.map((c) => c.id)), types, boundTypes };
|
|
33560
|
+
return { methodsOf, componentKeys: new Set(components.map((c) => c.id)), types, boundTypes, exportMethods };
|
|
32747
33561
|
}
|
|
32748
33562
|
function methodReading(tables, intf, value) {
|
|
32749
33563
|
const dot = value.lastIndexOf(".");
|
|
@@ -32759,6 +33573,11 @@ function methodReading(tables, intf, value) {
|
|
|
32759
33573
|
}
|
|
32760
33574
|
return null;
|
|
32761
33575
|
}
|
|
33576
|
+
function exportReading(tables, intf, value) {
|
|
33577
|
+
if (!value.includes("::")) return null;
|
|
33578
|
+
const method = tables.exportMethods.get(`${namespaceOf2(intf.id)}|${value}`);
|
|
33579
|
+
return method ? { target: value, method } : null;
|
|
33580
|
+
}
|
|
32762
33581
|
function typeReading(tables, intf, value) {
|
|
32763
33582
|
const bound2 = value.includes("::") ? tables.boundTypes.get(`${intf.id}|${value}`) : void 0;
|
|
32764
33583
|
const named2 = bound2 !== void 0 ? tables.types.filter((t) => t.id === bound2) : tables.types.filter((t) => t.id === value || typeMatchesRef(t, value));
|
|
@@ -32783,7 +33602,9 @@ function restatedFact(base, stored, params, returns) {
|
|
|
32783
33602
|
function resolveSourced(tables, intf, stored, facts) {
|
|
32784
33603
|
const source = stored.signatureFrom;
|
|
32785
33604
|
const base = { interfaceId: intf.id, component: intf.component, method: stored.name, source };
|
|
32786
|
-
const
|
|
33605
|
+
const local = methodReading(tables, intf, source);
|
|
33606
|
+
const external = local ? null : exportReading(tables, intf, source);
|
|
33607
|
+
const hit = local ?? external;
|
|
32787
33608
|
const { signature, other } = typeReading(tables, intf, source);
|
|
32788
33609
|
if (hit && signature) {
|
|
32789
33610
|
facts.sources.push({ ...base, outcome: "ambiguous", candidates: [hit.target, typeTarget(signature)] });
|
|
@@ -32797,7 +33618,7 @@ function resolveSourced(tables, intf, stored, facts) {
|
|
|
32797
33618
|
facts.sources.push({ ...base, form: "method", target: hit.target, outcome: "chained", detail: hit.method.signatureFrom });
|
|
32798
33619
|
return unresolvedMethod(stored);
|
|
32799
33620
|
}
|
|
32800
|
-
const form = hit ? "method" : "signature";
|
|
33621
|
+
const form = external ? "export" : hit ? "method" : "signature";
|
|
32801
33622
|
const target = hit ? hit.target : typeTarget(signature);
|
|
32802
33623
|
const params = hit ? hit.method.params : signature.params;
|
|
32803
33624
|
const returns = hit ? hit.method.returns : signature.returns ?? "unknown";
|
|
@@ -32816,10 +33637,10 @@ function withDerivedText(method, stored, specId, kind, facts) {
|
|
|
32816
33637
|
if (stored !== void 0 && stored !== derived) facts.staleTexts.push({ specId, kind, method: method.name, stored, derived });
|
|
32817
33638
|
return { ...method, signature: derived };
|
|
32818
33639
|
}
|
|
32819
|
-
function resolveTree(interfaces, components, types, references = []) {
|
|
33640
|
+
function resolveTree(interfaces, components, types, references = [], exportMethods = /* @__PURE__ */ new Map()) {
|
|
32820
33641
|
const intfs = interfaces.map((i) => ({ ...i, methods: i.methods.map((m) => ({ ...m })) }));
|
|
32821
33642
|
const typeCopies = types.map((t) => ({ ...t, methods: t.methods.map((m) => ({ ...m })) }));
|
|
32822
|
-
const tables = tablesOf(intfs, [...components], typeCopies, references);
|
|
33643
|
+
const tables = tablesOf(intfs, [...components], typeCopies, references, exportMethods);
|
|
32823
33644
|
const facts = emptySignatureFacts();
|
|
32824
33645
|
const sourced = intfs.map((intf) => ({
|
|
32825
33646
|
...intf,
|
|
@@ -32871,6 +33692,13 @@ function assertSpecWritesPermitted(owners, what) {
|
|
|
32871
33692
|
`SubsystemWriteDenied: ${what} would change ${denied.length === 1 ? "a spec" : "specs"} this request may not change \u2014 ${denied.join("; ")}. Nothing was written.`
|
|
32872
33693
|
);
|
|
32873
33694
|
}
|
|
33695
|
+
var TOLERATED_KEYS = /* @__PURE__ */ new Set(["schemaVersion"]);
|
|
33696
|
+
var UNKNOWN_KEY_HINTS = {
|
|
33697
|
+
"system.exports": "The L0 export table is `publicInterfaces` (sdd_set_public_interfaces writes it).",
|
|
33698
|
+
"system.export": "The L0 export table is `publicInterfaces` (sdd_set_public_interfaces writes it).",
|
|
33699
|
+
"subsystem.exports": "A subsystem publishes through `publicInterfaces`.",
|
|
33700
|
+
"component.type": "A component's stereotype is `componentType`."
|
|
33701
|
+
};
|
|
32874
33702
|
function unknownKeysIn(raw, parsed, at, out) {
|
|
32875
33703
|
if (Array.isArray(raw)) {
|
|
32876
33704
|
if (!Array.isArray(parsed) || parsed.length !== raw.length) return;
|
|
@@ -33165,11 +33993,11 @@ function inspectChainedRoots(rootDir = getProjectRoot()) {
|
|
|
33165
33993
|
continue;
|
|
33166
33994
|
}
|
|
33167
33995
|
if (member.form === "members" && !projectContentAt(childDir)) continue;
|
|
33168
|
-
const
|
|
33169
|
-
if (listed.has(
|
|
33170
|
-
listed.add(
|
|
33996
|
+
const dirKey2 = chainDirKey(childDir);
|
|
33997
|
+
if (listed.has(dirKey2)) continue;
|
|
33998
|
+
listed.add(dirKey2);
|
|
33171
33999
|
inspection.roots.push(path21.relative(root, childDir).split(path21.sep).join("/"));
|
|
33172
|
-
walk2(childDir, key2, /* @__PURE__ */ new Set([...ancestors,
|
|
34000
|
+
walk2(childDir, key2, /* @__PURE__ */ new Set([...ancestors, dirKey2]), depth + 1);
|
|
33173
34001
|
}
|
|
33174
34002
|
};
|
|
33175
34003
|
walk2(root, "", /* @__PURE__ */ new Set([chainDirKey(root)]), 0);
|
|
@@ -33592,6 +34420,70 @@ function keyedSystem(system, key2, subsystems) {
|
|
|
33592
34420
|
})
|
|
33593
34421
|
};
|
|
33594
34422
|
}
|
|
34423
|
+
function referrerSpelling(text3, rename) {
|
|
34424
|
+
if (text3 === void 0) return void 0;
|
|
34425
|
+
const parsed = parseTypeExpression(text3, "returns");
|
|
34426
|
+
if (!parsed.expression) return text3;
|
|
34427
|
+
const walk2 = (expr) => {
|
|
34428
|
+
const args = expr.args.map(walk2);
|
|
34429
|
+
return (expr.form === "named" || expr.form === "applied") && !isTypeVocabulary(expr.name) ? { ...expr, name: rename(expr.name), args } : { ...expr, args };
|
|
34430
|
+
};
|
|
34431
|
+
return canonicalTypeText(walk2(parsed.expression));
|
|
34432
|
+
}
|
|
34433
|
+
function spelledForReferrer(method, rename) {
|
|
34434
|
+
const { signatureFrom: _source, ...rest } = method;
|
|
34435
|
+
return {
|
|
34436
|
+
...rest,
|
|
34437
|
+
returns: referrerSpelling(method.returns, rename) ?? method.returns,
|
|
34438
|
+
...method.params ? { params: method.params.map((p) => ({ ...p, type: referrerSpelling(p.type, rename) ?? p.type })) } : {}
|
|
34439
|
+
};
|
|
34440
|
+
}
|
|
34441
|
+
function exportMethodsOf(raws) {
|
|
34442
|
+
const out = /* @__PURE__ */ new Map();
|
|
34443
|
+
const byKey3 = new Map(raws.map((r) => [r.record.namespace, r]));
|
|
34444
|
+
const last = (id) => id.split("::").pop().split(".").pop();
|
|
34445
|
+
for (const raw of raws) {
|
|
34446
|
+
const ns = raw.record.namespace;
|
|
34447
|
+
for (const [alias, key2] of raw.record.aliases) {
|
|
34448
|
+
const producer = byKey3.get(key2);
|
|
34449
|
+
const table = producer?.record.exports;
|
|
34450
|
+
if (!producer || !table) continue;
|
|
34451
|
+
const typeNames = /* @__PURE__ */ new Map();
|
|
34452
|
+
for (const e of table.entries) if (e.kind === "type" && e.typeDef) typeNames.set(last(e.typeDef), e.publicName);
|
|
34453
|
+
const rename = (name) => `${alias}::${typeNames.get(last(name)) ?? last(name)}`;
|
|
34454
|
+
for (const entry of table.entries) {
|
|
34455
|
+
if (entry.kind !== "component" || !entry.component) continue;
|
|
34456
|
+
const contracts = producer.index.interfaces.filter((i) => entry.interface ? i.id === entry.interface : i.component === entry.component);
|
|
34457
|
+
for (const method of contracts.flatMap((i) => i.methods)) {
|
|
34458
|
+
if (method.signatureFrom !== void 0) continue;
|
|
34459
|
+
out.set(`${ns}|${alias}::${entry.publicName}.${method.name}`, spelledForReferrer(method, rename));
|
|
34460
|
+
}
|
|
34461
|
+
}
|
|
34462
|
+
}
|
|
34463
|
+
const config = raw.record.config;
|
|
34464
|
+
if (!config) continue;
|
|
34465
|
+
for (const declared of declaredExternals(config)) {
|
|
34466
|
+
if (declared.problem || raw.record.aliases.has(declared.alias)) continue;
|
|
34467
|
+
let snapshot;
|
|
34468
|
+
try {
|
|
34469
|
+
const file = path21.join(aiPathsAt(raw.dir).root(), "externals", `${declared.alias}.yaml`);
|
|
34470
|
+
if (!fs15.existsSync(file)) continue;
|
|
34471
|
+
snapshot = SurfaceSnapshotSchema.parse(readYamlFile(file));
|
|
34472
|
+
} catch {
|
|
34473
|
+
continue;
|
|
34474
|
+
}
|
|
34475
|
+
const publicOf = new Map((snapshot.exportedTypes ?? []).map((t) => [t.type, t.id]));
|
|
34476
|
+
const closure = new Set(snapshot.types.map((t) => t.id));
|
|
34477
|
+
const rename = (name) => closure.has(name) ? `${declared.alias}::${publicOf.get(name) ?? last(name)}` : name;
|
|
34478
|
+
for (const entry of snapshot.interfaces) {
|
|
34479
|
+
for (const method of entry.methods) {
|
|
34480
|
+
out.set(`${ns}|${declared.alias}::${entry.id}.${method.name}`, spelledForReferrer(method, rename));
|
|
34481
|
+
}
|
|
34482
|
+
}
|
|
34483
|
+
}
|
|
34484
|
+
}
|
|
34485
|
+
return out;
|
|
34486
|
+
}
|
|
33595
34487
|
function resolveFamilyTables(raws) {
|
|
33596
34488
|
const byKey3 = new Map(raws.map((r) => [r.record.namespace, r]));
|
|
33597
34489
|
const done = /* @__PURE__ */ new Set();
|
|
@@ -33621,6 +34513,11 @@ function resolveFamilyTables(raws) {
|
|
|
33621
34513
|
producerTables,
|
|
33622
34514
|
audienceOf
|
|
33623
34515
|
});
|
|
34516
|
+
followProducerRenames(raw.record.exports, (source) => {
|
|
34517
|
+
const key3 = raw.record.aliases.get(source);
|
|
34518
|
+
const producer = key3 !== void 0 ? byKey3.get(key3) : void 0;
|
|
34519
|
+
return producer?.record.exports ? { table: producer.record.exports, specs: producer.index } : void 0;
|
|
34520
|
+
});
|
|
33624
34521
|
done.add(key2);
|
|
33625
34522
|
return raw.record.exports;
|
|
33626
34523
|
} finally {
|
|
@@ -34063,8 +34960,8 @@ function publicInterfaceKey(o) {
|
|
|
34063
34960
|
if (from || typeDef) {
|
|
34064
34961
|
const item = typeDef ? `type ${typeDef}` : str(o.component) ?? "*";
|
|
34065
34962
|
const narrowed2 = str(o.interface) ? `.${String(o.interface)}` : "";
|
|
34066
|
-
const
|
|
34067
|
-
return `${from ? `${from}:` : ""}${item}${narrowed2}${
|
|
34963
|
+
const renamed2 = str(o.as) ? ` as ${String(o.as)}` : "";
|
|
34964
|
+
return `${from ? `${from}:` : ""}${item}${narrowed2}${renamed2}`;
|
|
34068
34965
|
}
|
|
34069
34966
|
const component = str(o.component);
|
|
34070
34967
|
if (component) return str(o.interface) ? `${component}.${String(o.interface)}` : component;
|
|
@@ -34517,7 +35414,7 @@ var SpecWorkspace = class {
|
|
|
34517
35414
|
index.retiredReach.push(...raw.index.retiredReach.map((fact) => ({ ...fact, specId: keyIn(raw.record.namespace, fact.specId) })));
|
|
34518
35415
|
}
|
|
34519
35416
|
readRetiredReachAcrossSpecs(index);
|
|
34520
|
-
const signatures = resolveTree(index.interfaces, index.components, index.types, raws.flatMap((r) => r.record.authoredReferences));
|
|
35417
|
+
const signatures = resolveTree(index.interfaces, index.components, index.types, raws.flatMap((r) => r.record.authoredReferences), exportMethodsOf(raws));
|
|
34521
35418
|
index.interfaces = signatures.interfaces;
|
|
34522
35419
|
index.types = signatures.types;
|
|
34523
35420
|
index.signatures = {
|
|
@@ -34535,6 +35432,7 @@ var SpecWorkspace = class {
|
|
|
34535
35432
|
readRoot(dir, parent, member, ancestors, maxDepth, depth, raws, taken) {
|
|
34536
35433
|
const config = configAt(dir);
|
|
34537
35434
|
this.scanVisitedConfigFiles.push(aiPathsAt(dir).projectConfig());
|
|
35435
|
+
if (!parent && config) this.reportUnknownConfigKeys(aiPathsAt(dir).projectConfig(), rawConfigAt(dir), config);
|
|
34538
35436
|
const id = config ? effectiveProjectId(config) : null;
|
|
34539
35437
|
const aliasPath = parent && member ? keyIn(parent.aliasPath, member.alias) : "";
|
|
34540
35438
|
const key2 = !parent ? "" : id && !taken.has(id) ? id : aliasPath;
|
|
@@ -34639,6 +35537,7 @@ var SpecWorkspace = class {
|
|
|
34639
35537
|
const doc = readSpecFile(systemYaml);
|
|
34640
35538
|
if (doc && typeof doc === "object") raw.index.retiredReach.push(...readRetiredReachForms("system", doc));
|
|
34641
35539
|
raw.rawSystem = SystemSpecSchema.parse(doc);
|
|
35540
|
+
this.reportUnknownKeys(systemYaml, "system", doc, raw.rawSystem, keyIn(raw.record.namespace, "system"));
|
|
34642
35541
|
} catch {
|
|
34643
35542
|
raw.rawSystem = null;
|
|
34644
35543
|
}
|
|
@@ -34820,6 +35719,42 @@ var SpecWorkspace = class {
|
|
|
34820
35719
|
this.memberProblem(raw, "kind-mismatch", decl.alias, `the part "${decl.alias}" says it is a part of "${partOf.project}", but "${ownId}" declares it \u2014 correct \`partOf.project\` to "${ownId}" ${whereToEdit(decl, dir)}`);
|
|
34821
35720
|
}
|
|
34822
35721
|
}
|
|
35722
|
+
/**
|
|
35723
|
+
* A spec file's keys its schema does not know — which the parse drops, so
|
|
35724
|
+
* they would mean nothing without a word — reported as UNKNOWN_SPEC_KEY,
|
|
35725
|
+
* once per file. A retired reachability form is read compatibly and
|
|
35726
|
+
* reported by its own rule, so a component's `mounts` is no unknown key.
|
|
35727
|
+
*/
|
|
35728
|
+
reportUnknownKeys(file, kind, doc, parsed, specId) {
|
|
35729
|
+
const unknown = [];
|
|
35730
|
+
unknownKeysIn(doc, parsed, "", unknown);
|
|
35731
|
+
const keys = unknown.map((entry) => entry.slice(0, entry.indexOf(":"))).filter((key2) => !(kind === "component" && key2.startsWith("mounts")) && !TOLERATED_KEYS.has(key2));
|
|
35732
|
+
if (keys.length === 0) return;
|
|
35733
|
+
const hint = keys.map((key2) => UNKNOWN_KEY_HINTS[`${kind}.${key2}`]).filter(Boolean);
|
|
35734
|
+
this.loaderIssues.push({
|
|
35735
|
+
severity: "warning",
|
|
35736
|
+
code: "UNKNOWN_SPEC_KEY",
|
|
35737
|
+
message: `Spec file "${file}" holds ${keys.length === 1 ? "a key" : "keys"} its ${kind} schema does not know, which ${keys.length === 1 ? "is" : "are"} ignored: ${keys.join(", ")}. ${hint.length ? `${hint.join(" ")} ` : ""}Remove ${keys.length === 1 ? "it" : "them"}, or move what ${keys.length === 1 ? "it means" : "they mean"} into a field the schema has \u2014 a spec edited through the sdd_* tools never holds one.`,
|
|
35738
|
+
specId
|
|
35739
|
+
});
|
|
35740
|
+
}
|
|
35741
|
+
/**
|
|
35742
|
+
* The bound root's .wai/project.yaml keys its schema does not know, reported
|
|
35743
|
+
* as UNKNOWN_CONFIG_KEY (a warning): the configuration's parse drops them, so
|
|
35744
|
+
* a misspelt setting would otherwise silently configure nothing.
|
|
35745
|
+
*/
|
|
35746
|
+
reportUnknownConfigKeys(file, raw, config) {
|
|
35747
|
+
if (!raw) return;
|
|
35748
|
+
const unknown = [];
|
|
35749
|
+
unknownKeysIn(raw, config, "", unknown);
|
|
35750
|
+
const keys = unknown.map((entry) => entry.slice(0, entry.indexOf(":")));
|
|
35751
|
+
if (keys.length === 0) return;
|
|
35752
|
+
this.loaderIssues.push({
|
|
35753
|
+
severity: "warning",
|
|
35754
|
+
code: "UNKNOWN_CONFIG_KEY",
|
|
35755
|
+
message: `"${file}" holds ${keys.length === 1 ? "a setting" : "settings"} wairon does not know, which ${keys.length === 1 ? "is" : "are"} ignored: ${keys.join(", ")}. Check the spelling against docs/cli.md (configuration) \u2014 a misspelt setting configures nothing.`
|
|
35756
|
+
});
|
|
35757
|
+
}
|
|
34823
35758
|
/**
|
|
34824
35759
|
* Read every spec document under a specs directory into a root's index, as
|
|
34825
35760
|
* written: the root's own (`systemYaml` is its L0, skipped here) or a part's
|
|
@@ -34868,6 +35803,7 @@ var SpecWorkspace = class {
|
|
|
34868
35803
|
if ("parentSystem" in doc) {
|
|
34869
35804
|
detectedType = "subsystem";
|
|
34870
35805
|
const parsed = SubsystemSpecSchema.parse(doc);
|
|
35806
|
+
this.reportUnknownKeys(file, "subsystem", doc, parsed, keyIn(key2, parsed.id));
|
|
34871
35807
|
if (!(parsed.projectPath && parsed.projectPath.trim() !== "")) {
|
|
34872
35808
|
index.retiredReach.push(...readRetiredReachForms("subsystem", doc));
|
|
34873
35809
|
}
|
|
@@ -34877,17 +35813,20 @@ var SpecWorkspace = class {
|
|
|
34877
35813
|
detectedType = "component";
|
|
34878
35814
|
const facts = readRetiredReachForms("component", doc);
|
|
34879
35815
|
const parsed = attachRetiredMounts(ComponentSpecSchema.parse(doc), doc);
|
|
35816
|
+
this.reportUnknownKeys(file, "component", doc, parsed, keyIn(key2, parsed.id));
|
|
34880
35817
|
index.retiredReach.push(...facts);
|
|
34881
35818
|
keep("component", index.components, parsed, file);
|
|
34882
35819
|
} else if ("component" in doc) {
|
|
34883
35820
|
detectedType = "interface";
|
|
34884
35821
|
const facts = readRetiredReachForms("interface", doc);
|
|
34885
35822
|
const parsed = InterfaceSpecSchema.parse(doc);
|
|
35823
|
+
this.reportUnknownKeys(file, "interface", doc, parsed, keyIn(key2, parsed.id));
|
|
34886
35824
|
index.retiredReach.push(...facts);
|
|
34887
35825
|
keep("interface", index.interfaces, parsed, file);
|
|
34888
35826
|
} else if ("contract" in doc) {
|
|
34889
35827
|
detectedType = "implementation";
|
|
34890
35828
|
const parsed = ImplementationSpecSchema.parse(doc);
|
|
35829
|
+
this.reportUnknownKeys(file, "implementation", doc, parsed, keyIn(key2, parsed.id));
|
|
34891
35830
|
const normalizeSourcePath = (p) => path21.relative(raw.dir, path21.resolve(baseDir, p)).replace(/\\/g, "/");
|
|
34892
35831
|
if (parsed.sourcePath) parsed.sourcePath = normalizeSourcePath(parsed.sourcePath);
|
|
34893
35832
|
for (const method of parsed.methods) {
|
|
@@ -34901,7 +35840,9 @@ var SpecWorkspace = class {
|
|
|
34901
35840
|
keep("group", index.groups, GroupSpecSchema.parse(doc), file);
|
|
34902
35841
|
} else {
|
|
34903
35842
|
detectedType = "type";
|
|
34904
|
-
|
|
35843
|
+
const parsed = TypeSpecSchema.parse(doc);
|
|
35844
|
+
this.reportUnknownKeys(file, "type", doc, parsed, keyIn(key2, parsed.id));
|
|
35845
|
+
keep("type", index.types, parsed, file);
|
|
34905
35846
|
}
|
|
34906
35847
|
}
|
|
34907
35848
|
if (detectedType === "spec") {
|
|
@@ -36434,8 +37375,8 @@ var SpecWorkspace = class {
|
|
|
36434
37375
|
if (kind !== "interface" && kind !== "type") return [];
|
|
36435
37376
|
const read2 = kind === "interface" ? interfaceCanonicalTypes(merged) : typeCanonicalTypes(merged);
|
|
36436
37377
|
Object.assign(merged, read2.spec);
|
|
36437
|
-
const
|
|
36438
|
-
const fromFile = this.scanAll().typeSpellings.respellings.filter((r) => r.specId === merged.id && r.kind === kind && !
|
|
37378
|
+
const respelled2 = new Set(read2.respellings.map((r) => r.path));
|
|
37379
|
+
const fromFile = this.scanAll().typeSpellings.respellings.filter((r) => r.specId === merged.id && r.kind === kind && !respelled2.has(r.path) && typePositionText(merged, r.path) === r.stored);
|
|
36439
37380
|
return [...read2.respellings, ...fromFile];
|
|
36440
37381
|
}
|
|
36441
37382
|
/**
|
|
@@ -36562,6 +37503,18 @@ var SpecWorkspace = class {
|
|
|
36562
37503
|
if (!result) {
|
|
36563
37504
|
throw new Error(`Spec of kind "${kind}" with ID "${id}" does not exist. Define it first.`);
|
|
36564
37505
|
}
|
|
37506
|
+
if (kind !== "system") {
|
|
37507
|
+
const stored = String(result.id ?? id);
|
|
37508
|
+
const member = this.getSubprojectPrefix(stored) ?? this.getSubprojectPrefix(id);
|
|
37509
|
+
if (member !== null && this.rootCovering(member)?.legacyMount === null) {
|
|
37510
|
+
const local = stored.startsWith(`${member}::`) ? stored.slice(member.length + 2) : stored;
|
|
37511
|
+
const dir = this.resolveSubprojectForNamespace(member);
|
|
37512
|
+
const folder = dir ? path21.relative(this.rootDir, dir).split(path21.sep).join("/") || "." : void 0;
|
|
37513
|
+
throw new Error(
|
|
37514
|
+
`chained-spec: "${stored}" lives in another project; update it from that project's own root. It belongs to the member project "${member}"${folder ? ` at ${folder}` : ""}: open a session in that folder (its own guide and .mcp.json \u2014 run \`wairon generate\` and \`wairon mcp install --backend claude\` there first if it has none) and call sdd_update_spec ${kind} ${local} there. Nothing was written.`
|
|
37515
|
+
);
|
|
37516
|
+
}
|
|
37517
|
+
}
|
|
36565
37518
|
if (kind === "system" && id && id !== "system" && id !== result.name) {
|
|
36566
37519
|
throw new Error(
|
|
36567
37520
|
`kind "system" targets the singleton L0 spec (system name "${result.name}") \u2014 pass id "system" or the system name, got "${id}". For a subsystem, use kind "subsystem".`
|
|
@@ -36821,12 +37774,12 @@ var SpecWorkspace = class {
|
|
|
36821
37774
|
1,
|
|
36822
37775
|
capture
|
|
36823
37776
|
));
|
|
36824
|
-
const { action, remove:
|
|
37777
|
+
const { action, remove: remove3, captureJumps, ...cleanStep } = deltaStep;
|
|
36825
37778
|
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
36826
37779
|
} else {
|
|
36827
37780
|
const idx = steps.findIndex((s) => s.stepNumber === stepNum);
|
|
36828
37781
|
if (idx !== -1) {
|
|
36829
|
-
const { action, remove:
|
|
37782
|
+
const { action, remove: remove3, ...rawStep } = deltaStep;
|
|
36830
37783
|
const stored = steps[idx];
|
|
36831
37784
|
const cleanStep = pinDeltaJumps(rawStep, stored[DELTA_JUMP_PINS]);
|
|
36832
37785
|
const merged = withUnset(retargetByLabel(mergeElement(stored, cleanStep), cleanStep), deltaStep);
|
|
@@ -36834,7 +37787,7 @@ var SpecWorkspace = class {
|
|
|
36834
37787
|
rebuilt[DELTA_JUMP_PINS] = cleanStep[DELTA_JUMP_PINS];
|
|
36835
37788
|
steps[idx] = rebuilt;
|
|
36836
37789
|
} else {
|
|
36837
|
-
const { action, remove:
|
|
37790
|
+
const { action, remove: remove3, ...cleanStep } = deltaStep;
|
|
36838
37791
|
steps.push(withUnset(pinDeltaJumps(cleanStep), deltaStep));
|
|
36839
37792
|
}
|
|
36840
37793
|
}
|
|
@@ -36931,7 +37884,7 @@ var SpecWorkspace = class {
|
|
|
36931
37884
|
merged.push(withUnset(deltaItem, deltaItem));
|
|
36932
37885
|
}
|
|
36933
37886
|
}
|
|
36934
|
-
return merged.map(({ action, remove:
|
|
37887
|
+
return merged.map(({ action, remove: remove3, ...item }) => item);
|
|
36935
37888
|
};
|
|
36936
37889
|
const mergePublicInterfaces = (existing, delta2) => {
|
|
36937
37890
|
const merged = [...existing];
|
|
@@ -36977,7 +37930,7 @@ var SpecWorkspace = class {
|
|
|
36977
37930
|
merged.push(withUnset(deltaItem, deltaItem));
|
|
36978
37931
|
}
|
|
36979
37932
|
}
|
|
36980
|
-
return merged.map((item) => item && typeof item === "object" ? (({ action, remove:
|
|
37933
|
+
return merged.map((item) => item && typeof item === "object" ? (({ action, remove: remove3, ...rest }) => rest)(item) : item);
|
|
36981
37934
|
};
|
|
36982
37935
|
const isIdentifiedArray = (field, a, b) => a.every((i) => identityKeyOf(field, i) !== null) && b.every((i) => identityKeyOf(field, i) !== null);
|
|
36983
37936
|
const mergeDelta = (existing, delta2) => {
|
|
@@ -37965,6 +38918,9 @@ function setProjectType(projectType) {
|
|
|
37965
38918
|
function describeProject(name, description) {
|
|
37966
38919
|
projectConfigRepository.describeProject(name, description);
|
|
37967
38920
|
}
|
|
38921
|
+
function setNetwork(network) {
|
|
38922
|
+
return projectConfigRepository.setNetwork(network);
|
|
38923
|
+
}
|
|
37968
38924
|
function recordProfileSelection(selection) {
|
|
37969
38925
|
projectConfigRepository.recordProfileSelection(selection);
|
|
37970
38926
|
}
|
|
@@ -38167,7 +39123,7 @@ function resolveHosted(external) {
|
|
|
38167
39123
|
const root = lookup2(external.hosted);
|
|
38168
39124
|
return root === null ? unresolved(`the hosted producer \`${external.hosted}\` is unknown or outside this request's reach`) : { ...external, directory: path23.resolve(root) };
|
|
38169
39125
|
}
|
|
38170
|
-
function
|
|
39126
|
+
function dirKey(dir) {
|
|
38171
39127
|
const resolved = path23.resolve(dir);
|
|
38172
39128
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
38173
39129
|
}
|
|
@@ -38179,11 +39135,11 @@ function climb(bound2) {
|
|
|
38179
39135
|
top = path23.resolve(parent.parentRoot);
|
|
38180
39136
|
}
|
|
38181
39137
|
const reach2 = getRequestParentReach();
|
|
38182
|
-
const stoppedAtNarrowedCeiling = !!reach2?.narrowed && reach2.topRoot !== void 0 &&
|
|
39138
|
+
const stoppedAtNarrowedCeiling = !!reach2?.narrowed && reach2.topRoot !== void 0 && dirKey(top) === dirKey(reach2.topRoot);
|
|
38183
39139
|
return { top, whole: !(reach2 && !reach2.parentReach) && !stoppedAtNarrowedCeiling };
|
|
38184
39140
|
}
|
|
38185
39141
|
function consumerNode(family, bound2) {
|
|
38186
|
-
return family.nodes.find((n) =>
|
|
39142
|
+
return family.nodes.find((n) => dirKey(n.directory) === dirKey(bound2));
|
|
38187
39143
|
}
|
|
38188
39144
|
function gitDeclarationOf(config, external) {
|
|
38189
39145
|
if (!config) return null;
|
|
@@ -38214,7 +39170,7 @@ function resolveGit(external, config, offline) {
|
|
|
38214
39170
|
}
|
|
38215
39171
|
var NOT_COMPARED_OFFLINE = "not compared offline \u2014 `wairon externals status` fetches it";
|
|
38216
39172
|
function consumerConfig(bound2) {
|
|
38217
|
-
return listProjectRoots().find((r) =>
|
|
39173
|
+
return listProjectRoots().find((r) => dirKey(r.directory) === dirKey(bound2))?.config ?? null;
|
|
38218
39174
|
}
|
|
38219
39175
|
function resolveDeclared(offline) {
|
|
38220
39176
|
const bound2 = path23.resolve(getProjectRoot());
|
|
@@ -38250,6 +39206,51 @@ function headAcross2(consumerDir, external) {
|
|
|
38250
39206
|
const head2 = head(external.directory);
|
|
38251
39207
|
return head2 ? { ...external, commit: head2 } : external;
|
|
38252
39208
|
}
|
|
39209
|
+
function resolveCandidate(alias, declaration2) {
|
|
39210
|
+
const bound2 = path23.resolve(getProjectRoot());
|
|
39211
|
+
const { top, whole } = climb(bound2);
|
|
39212
|
+
const config = consumerConfig(bound2);
|
|
39213
|
+
const answer = (root) => runWithProjectRoot(root, () => {
|
|
39214
|
+
const family = graph();
|
|
39215
|
+
const node = consumerNode(family, bound2);
|
|
39216
|
+
if (!node) return null;
|
|
39217
|
+
const [declared] = declaredExternals({ externals: { [alias]: declaration2 }, ...config?.members ? { members: config.members } : {} });
|
|
39218
|
+
const candidate = bindDeclaredExternal(declared, node, family.nodes);
|
|
39219
|
+
const external = candidate.sourceKind === "git" ? resolveGit(candidate, { externals: { [alias]: declaration2 } }, false) : headAcross2(node.directory, resolveHosted(candidate));
|
|
39220
|
+
return { external, reachable: whole };
|
|
39221
|
+
});
|
|
39222
|
+
return answer(top) ?? answer(bound2) ?? {
|
|
39223
|
+
external: { alias, project: declaration2.project ?? alias, sourceKind: "unresolved", audience: "instance", role: "external", problem: "the bound project is not in the family graph its root reads" },
|
|
39224
|
+
reachable: whole
|
|
39225
|
+
};
|
|
39226
|
+
}
|
|
39227
|
+
function listConsumers() {
|
|
39228
|
+
const bound2 = path23.resolve(getProjectRoot());
|
|
39229
|
+
const { top } = climb(bound2);
|
|
39230
|
+
return runWithProjectRoot(top, () => {
|
|
39231
|
+
const family = graph();
|
|
39232
|
+
const producer = consumerNode(family, bound2);
|
|
39233
|
+
if (!producer) return [];
|
|
39234
|
+
const consumers = [];
|
|
39235
|
+
for (const node of family.nodes) {
|
|
39236
|
+
if (node === producer) continue;
|
|
39237
|
+
const external = node.externals.find((e) => e.sourceKind === "family" && e.producer === producer.namespace);
|
|
39238
|
+
const memberAlias = [...node.aliases.entries()].find(([alias2, key2]) => key2 === producer.namespace && !node.externals.some((e) => e.alias === alias2))?.[0];
|
|
39239
|
+
const alias = external?.alias ?? memberAlias;
|
|
39240
|
+
if (alias === void 0) continue;
|
|
39241
|
+
const usage = exportUsage(node.namespace, producer.namespace);
|
|
39242
|
+
consumers.push({
|
|
39243
|
+
project: node.id ?? node.namespace,
|
|
39244
|
+
key: node.namespace,
|
|
39245
|
+
directory: node.directory,
|
|
39246
|
+
alias,
|
|
39247
|
+
section: external ? "externals" : "members",
|
|
39248
|
+
names: usage.used.map((u) => u.publicName).sort()
|
|
39249
|
+
});
|
|
39250
|
+
}
|
|
39251
|
+
return consumers.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
39252
|
+
});
|
|
39253
|
+
}
|
|
38253
39254
|
|
|
38254
39255
|
// src/core/part-context.ts
|
|
38255
39256
|
var crypto6 = __toESM(require("crypto"));
|
|
@@ -39073,7 +40074,7 @@ function promoteMember(alias, id) {
|
|
|
39073
40074
|
patchKeepingStamp(s.file, (raw) => raw.parentSystem === projectName ? false : (raw.parentSystem = projectName, true));
|
|
39074
40075
|
}
|
|
39075
40076
|
if (declaration2?.as === "part") projectConfigRepository.updateMember(alias, { as: "" });
|
|
39076
|
-
const result = { respelled: [], memberRespelled: [], exported: [], imported: [], consumersDropped: [] };
|
|
40077
|
+
const result = { respelled: [], memberRespelled: [], exported: [], imported: [], consumersDropped: [], entriesDeclared: [], topicsCrossing: [] };
|
|
39077
40078
|
const parentSide = rewriteRefsIn(crossing.in.map((c) => c.spec).filter(unique), (ref, position) => INTO_MEMBER_POSITIONS.has(position) && crossing.insideTargets.has(ref) ? `${alias}::${ref}` : ref, { result, member: false });
|
|
39078
40079
|
const climbs = climbsOf(family, crossing.insideIds, parentId).map;
|
|
39079
40080
|
const climbing = inside.filter((s) => family.authoredReferences.some((r) => r.form === "super" && r.specId === s.id));
|
|
@@ -39081,6 +40082,8 @@ function promoteMember(alias, id) {
|
|
|
39081
40082
|
const qualified = respellQualified(inside, outside, { alias, parentId, newId }, result);
|
|
39082
40083
|
invalidateSpecCache();
|
|
39083
40084
|
dropCrossingConsumers(inside, outside, result);
|
|
40085
|
+
declareCrossingEntries(inside, outside, parentId || parentName, result);
|
|
40086
|
+
recordCrossingTopics(inside, outside, parentId || parentName, result);
|
|
39084
40087
|
if (crossing.out.length > 0 || qualified.out.length > 0 || [...climbs.values()].some((t) => t.startsWith(`${parentId}::`))) runWithProjectRoot(dir, () => projectConfigRepository.declareExternal(parentId, {}));
|
|
39085
40088
|
for (const s of inside.filter((x) => x.kind === "subsystem")) restateReExports(s.id, alias);
|
|
39086
40089
|
exportCrossings([...crossing.out, ...qualified.out], dir, alias, parentId, "out", result);
|
|
@@ -39106,6 +40109,92 @@ function promoteMember(alias, id) {
|
|
|
39106
40109
|
invalidateSpecCache();
|
|
39107
40110
|
return result;
|
|
39108
40111
|
}
|
|
40112
|
+
function callsInto2(callers, portals) {
|
|
40113
|
+
const componentOf = new Map(callers.filter((s) => s.kind === "interface").map((s) => [s.id, String(s.raw.component ?? "")]));
|
|
40114
|
+
const out = /* @__PURE__ */ new Map();
|
|
40115
|
+
const add2 = (target, verb, caller) => {
|
|
40116
|
+
const portal = target?.includes("::") ? target.slice(target.lastIndexOf("::") + 2) : target;
|
|
40117
|
+
if (!portal || !verb || !portals.has(portal)) return;
|
|
40118
|
+
const verbs = out.get(portal) ?? /* @__PURE__ */ new Map();
|
|
40119
|
+
verbs.set(verb, /* @__PURE__ */ new Set([...verbs.get(verb) ?? [], caller]));
|
|
40120
|
+
out.set(portal, verbs);
|
|
40121
|
+
};
|
|
40122
|
+
for (const impl of callers.filter((s) => s.kind === "implementation")) {
|
|
40123
|
+
const caller = componentOf.get(String(impl.raw.contract ?? ""));
|
|
40124
|
+
if (!caller) continue;
|
|
40125
|
+
for (const method of Array.isArray(impl.raw.methods) ? impl.raw.methods : []) {
|
|
40126
|
+
for (const step of Array.isArray(method?.narrative) ? method.narrative : []) {
|
|
40127
|
+
if (step?.type === "call" || step?.type === "register") add2(step.targetComponent, step.targetMethod, caller);
|
|
40128
|
+
}
|
|
40129
|
+
for (const entry of Array.isArray(method?.calls) ? method.calls : []) {
|
|
40130
|
+
const call = typeof entry === "string" ? parseDeclaredCall(entry) : null;
|
|
40131
|
+
if (call) add2(call.compId, call.methodName, caller);
|
|
40132
|
+
}
|
|
40133
|
+
}
|
|
40134
|
+
}
|
|
40135
|
+
return out;
|
|
40136
|
+
}
|
|
40137
|
+
function declareCrossingEntries(inside, outside, parent, result) {
|
|
40138
|
+
const portals = new Map(inside.filter((s) => s.kind === "component" && s.raw.componentType === "Portal").map((s) => [s.id, s]));
|
|
40139
|
+
const called = callsInto2(outside, new Set(portals.keys()));
|
|
40140
|
+
for (const [portalId, verbs] of [...called].sort(([a], [b]) => a.localeCompare(b))) {
|
|
40141
|
+
const portal = portals.get(portalId);
|
|
40142
|
+
if (portal.raw.invokedBy?.kind === "entry") continue;
|
|
40143
|
+
const transport = portal.raw.transport;
|
|
40144
|
+
const network = transport !== void 0 && transportKind(transport) === "network";
|
|
40145
|
+
for (const contract of inside.filter((s) => s.kind === "interface" && s.raw.component === portalId)) {
|
|
40146
|
+
patchKeepingStamp(contract.file, (raw) => {
|
|
40147
|
+
let changed = false;
|
|
40148
|
+
for (const method of Array.isArray(raw.methods) ? raw.methods : []) {
|
|
40149
|
+
const callers = verbs.get(method?.name);
|
|
40150
|
+
if (!callers || method.invokedBy !== void 0) continue;
|
|
40151
|
+
const names = [...callers].sort().join(", ");
|
|
40152
|
+
method.invokedBy = {
|
|
40153
|
+
kind: "entry",
|
|
40154
|
+
...network ? { scope: "network" } : {},
|
|
40155
|
+
caller: `${names} of ${parent}, calling ${portalId}.${method.name} across the project boundary (modelled calls in ${parent}; declared when this project was promoted out of it)`
|
|
40156
|
+
};
|
|
40157
|
+
result.entriesDeclared.push(`${portalId}.${method.name}: entry${network ? " (network)" : ""} \u2014 called by ${names} of ${parent}`);
|
|
40158
|
+
changed = true;
|
|
40159
|
+
}
|
|
40160
|
+
return changed;
|
|
40161
|
+
});
|
|
40162
|
+
}
|
|
40163
|
+
}
|
|
40164
|
+
}
|
|
40165
|
+
function topicEnds(specs) {
|
|
40166
|
+
const emits = /* @__PURE__ */ new Map();
|
|
40167
|
+
const subscribes = /* @__PURE__ */ new Map();
|
|
40168
|
+
const add2 = (into, topic, component) => {
|
|
40169
|
+
if (typeof topic !== "string" || topic === "") return;
|
|
40170
|
+
into.set(topic, /* @__PURE__ */ new Set([...into.get(topic) ?? [], component]));
|
|
40171
|
+
};
|
|
40172
|
+
for (const c of specs.filter((s) => s.kind === "component")) {
|
|
40173
|
+
for (const e of Array.isArray(c.raw.emits) ? c.raw.emits : []) add2(emits, e?.topic, c.id);
|
|
40174
|
+
for (const e of Array.isArray(c.raw.subscribesTo) ? c.raw.subscribesTo : []) add2(subscribes, e?.topic, c.id);
|
|
40175
|
+
}
|
|
40176
|
+
for (const i of specs.filter((s) => s.kind === "interface")) {
|
|
40177
|
+
for (const m of Array.isArray(i.raw.methods) ? i.raw.methods : []) {
|
|
40178
|
+
const ep = m?.endpoint;
|
|
40179
|
+
if (ep?.transport !== "MessageBus") continue;
|
|
40180
|
+
add2(ep.direction === "publish" ? emits : subscribes, ep.topic, String(i.raw.component ?? i.id));
|
|
40181
|
+
}
|
|
40182
|
+
}
|
|
40183
|
+
return { emits, subscribes };
|
|
40184
|
+
}
|
|
40185
|
+
function recordCrossingTopics(inside, outside, parent, result) {
|
|
40186
|
+
const mine = topicEnds(inside);
|
|
40187
|
+
const theirs = topicEnds(outside);
|
|
40188
|
+
const list = (ids) => [...ids ?? []].sort().join(", ");
|
|
40189
|
+
for (const [topic, emitters] of [...mine.emits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
40190
|
+
if (mine.subscribes.has(topic) || !theirs.subscribes.has(topic)) continue;
|
|
40191
|
+
result.topicsCrossing.push(`${topic}: emitted by ${list(emitters)} (the new project), consumed in ${parent} by ${list(theirs.subscribes.get(topic))} \u2014 the family run pairs it; the new project's own gate alone reports UNCONSUMED_TOPIC`);
|
|
40192
|
+
}
|
|
40193
|
+
for (const [topic, subscribers] of [...mine.subscribes].sort(([a], [b]) => a.localeCompare(b))) {
|
|
40194
|
+
if (mine.emits.has(topic) || !theirs.emits.has(topic)) continue;
|
|
40195
|
+
result.topicsCrossing.push(`${topic}: consumed by ${list(subscribers)} (the new project), emitted in ${parent} by ${list(theirs.emits.get(topic))} \u2014 the family run pairs it; the new project's own gate alone reports UNSOURCED_SUBSCRIPTION`);
|
|
40196
|
+
}
|
|
40197
|
+
}
|
|
39109
40198
|
function recordRespelling(result, edit4, member) {
|
|
39110
40199
|
if (edit4.from === edit4.to) return;
|
|
39111
40200
|
const list = member ? result.memberRespelled : result.respelled;
|
|
@@ -40027,7 +41116,9 @@ function rebaseImplementationPaths(specsDir, fromRoot, toRoot, only) {
|
|
|
40027
41116
|
} catch {
|
|
40028
41117
|
continue;
|
|
40029
41118
|
}
|
|
40030
|
-
if (!raw || typeof raw !== "object"
|
|
41119
|
+
if (!raw || typeof raw !== "object") continue;
|
|
41120
|
+
const isImplementation = "contract" in raw;
|
|
41121
|
+
if (!isImplementation && specKind(raw) !== "type") continue;
|
|
40031
41122
|
let changed = false;
|
|
40032
41123
|
const rebase = (holder, key2) => {
|
|
40033
41124
|
const p = holder[key2];
|
|
@@ -40038,7 +41129,7 @@ function rebaseImplementationPaths(specsDir, fromRoot, toRoot, only) {
|
|
|
40038
41129
|
changed = true;
|
|
40039
41130
|
}
|
|
40040
41131
|
};
|
|
40041
|
-
for (const key2 of ["sourcePath", "simPath"]) rebase(raw, key2);
|
|
41132
|
+
for (const key2 of isImplementation ? ["sourcePath", "simPath"] : ["sourcePath"]) rebase(raw, key2);
|
|
40042
41133
|
if (Array.isArray(raw.methods)) {
|
|
40043
41134
|
for (const method of raw.methods) {
|
|
40044
41135
|
if (method && typeof method === "object") rebase(method, "sourcePath");
|
|
@@ -40088,13 +41179,13 @@ function renameComponent(componentId, newId) {
|
|
|
40088
41179
|
const contracts = interfaces.filter((i) => i.component === componentId);
|
|
40089
41180
|
const movingInterface = contracts.find((i) => i.id === `i${componentId}`);
|
|
40090
41181
|
const movingImplementation = implementations.find((impl) => impl.id === `${componentId}_impl` && contracts.some((i) => i.id === impl.contract));
|
|
40091
|
-
const
|
|
41182
|
+
const renamed2 = [
|
|
40092
41183
|
{ kind: "component", from: componentId, to: newId },
|
|
40093
41184
|
...movingInterface ? [{ kind: "interface", from: movingInterface.id, to: interfaceId }] : [],
|
|
40094
41185
|
...movingImplementation ? [{ kind: "implementation", from: movingImplementation.id, to: implementationId }] : []
|
|
40095
41186
|
];
|
|
40096
41187
|
const rename = {
|
|
40097
|
-
specs:
|
|
41188
|
+
specs: renamed2.map(({ from, to }) => ({ from, to })),
|
|
40098
41189
|
components: [{ from: componentId, to: newId }]
|
|
40099
41190
|
};
|
|
40100
41191
|
const registerEdits = projectConfigRepository.rekeyCarried(rename, true);
|
|
@@ -40109,18 +41200,18 @@ function renameComponent(componentId, newId) {
|
|
|
40109
41200
|
const derivesName = (entry) => entry.interface !== void 0 ? movingInterface !== void 0 && entry.interface === movingInterface.id : entry.typeDef === void 0 && entry.component === componentId;
|
|
40110
41201
|
const keptDry = keepPublicNames(specsDir, derivesName, true);
|
|
40111
41202
|
assertSpecsInReach(
|
|
40112
|
-
[...
|
|
41203
|
+
[...renamed2.map(({ kind, from }) => ({ kind, id: from })), ...keptDry.specs, ...rewriteRefFields(specsDir, remap, void 0, true), ...rekeyLintAllows(specsDir, rename, true)],
|
|
40113
41204
|
`renaming component "${componentId}"`,
|
|
40114
41205
|
Array.isArray(registerEdits) && registerEdits.length > 0
|
|
40115
41206
|
);
|
|
40116
41207
|
const kept = keepPublicNames(specsDir, derivesName);
|
|
40117
41208
|
const rewrittenRefs = [...kept.specs, ...rewriteRefFields(specsDir, remap)];
|
|
40118
41209
|
const allowsRekeyed = rekeyLintAllows(specsDir, rename);
|
|
40119
|
-
const rewritten = [...new Set([...rewrittenRefs, ...allowsRekeyed].filter((spec) => !
|
|
41210
|
+
const rewritten = [...new Set([...rewrittenRefs, ...allowsRekeyed].filter((spec) => !renamed2.some((moved) => moved.kind === spec.kind && moved.from === spec.id)).map((spec) => spec.id))];
|
|
40120
41211
|
invalidateSpecCache();
|
|
40121
41212
|
moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation);
|
|
40122
41213
|
const carried = projectConfigRepository.rekeyCarried(rename);
|
|
40123
|
-
return { renamed, rewritten, carried, keptPublicNames: kept.names };
|
|
41214
|
+
return { renamed: renamed2, rewritten, carried, keptPublicNames: kept.names };
|
|
40124
41215
|
}
|
|
40125
41216
|
function moveRenamedSpecs(componentId, newId, component, movingInterface, movingImplementation) {
|
|
40126
41217
|
const interfaceId = `i${newId}`;
|
|
@@ -40269,7 +41360,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
40269
41360
|
}
|
|
40270
41361
|
const implementations = loadImplementationSpecs();
|
|
40271
41362
|
const movingContracts = new Set(moving.map((i) => i.id));
|
|
40272
|
-
const
|
|
41363
|
+
const renamed2 = [];
|
|
40273
41364
|
const rename = {
|
|
40274
41365
|
methods: [{
|
|
40275
41366
|
component: componentId,
|
|
@@ -40304,7 +41395,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
40304
41395
|
...m.params === void 0 && m.signatureFrom === void 0 ? { signature: renameInSignature(m.signature, methodName, newName) } : {}
|
|
40305
41396
|
} : m)
|
|
40306
41397
|
});
|
|
40307
|
-
|
|
41398
|
+
renamed2.push(contract.id);
|
|
40308
41399
|
}
|
|
40309
41400
|
let pinnedSymbol;
|
|
40310
41401
|
for (const implementation of implementations) {
|
|
@@ -40317,7 +41408,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
40317
41408
|
...implementation,
|
|
40318
41409
|
methods: implementation.methods.map((m) => m.name === methodName ? { ...m, name: newName, ...pin2 ? { symbol: methodName } : {} } : m)
|
|
40319
41410
|
});
|
|
40320
|
-
|
|
41411
|
+
renamed2.push(implementation.id);
|
|
40321
41412
|
}
|
|
40322
41413
|
invalidateSpecCache();
|
|
40323
41414
|
const specsDir = aiPathsAt(getProjectRoot()).specsDir();
|
|
@@ -40331,7 +41422,7 @@ function renameMethod(componentId, methodName, newName, pinSymbol) {
|
|
|
40331
41422
|
component: componentId,
|
|
40332
41423
|
from: methodName,
|
|
40333
41424
|
to: newName,
|
|
40334
|
-
renamed,
|
|
41425
|
+
renamed: renamed2,
|
|
40335
41426
|
rewritten,
|
|
40336
41427
|
mentions,
|
|
40337
41428
|
...pinnedSymbol ? { pinnedSymbol } : {},
|
|
@@ -40522,9 +41613,9 @@ function renameType(typeId, newId) {
|
|
|
40522
41613
|
Array.isArray(registerEdits) && registerEdits.length > 0
|
|
40523
41614
|
);
|
|
40524
41615
|
const kept = keepPublicNames(specsDir, derivesName);
|
|
40525
|
-
const
|
|
41616
|
+
const respelled2 = respellAll(false);
|
|
40526
41617
|
const allowsRekeyed = rekeyLintAllows(specsDir, rename);
|
|
40527
|
-
const rewritten = [...new Set([...kept.specs, ...
|
|
41618
|
+
const rewritten = [...new Set([...kept.specs, ...respelled2, ...allowsRekeyed].filter((spec) => !(spec.kind === "type" && spec.id === oldId)).map((spec) => spec.id))];
|
|
40528
41619
|
invalidateSpecCache();
|
|
40529
41620
|
const moved = loadTypeSpecs().find((t) => t.id === oldId && (t.subsystem ?? "") === (type.subsystem ?? "")) ?? type;
|
|
40530
41621
|
saveSpec("type", { ...moved, id: newId, previousIds: traced(moved.previousIds, oldId) });
|
|
@@ -40541,6 +41632,81 @@ function renameType(typeId, newId) {
|
|
|
40541
41632
|
carried
|
|
40542
41633
|
};
|
|
40543
41634
|
}
|
|
41635
|
+
function renameField(typeId, field, newName) {
|
|
41636
|
+
const all = loadTypeSpecs();
|
|
41637
|
+
const cut = typeId.lastIndexOf("::");
|
|
41638
|
+
const qualifier = cut >= 0 ? typeId.slice(0, cut) : void 0;
|
|
41639
|
+
const ownSubsystem = qualifier !== void 0 && loadSubsystemSpecs().some((s) => s.id === qualifier);
|
|
41640
|
+
if (qualifier !== void 0 && !ownSubsystem) {
|
|
41641
|
+
throw new WaironError(`chained-type: "${typeId}" lives in another project; rename its field from that project's own root. ${memberRootWay(typeId, `sdd_rename_field ${typeId.slice(cut + 2)} ${field} <new name>`)}`);
|
|
41642
|
+
}
|
|
41643
|
+
const type = qualifier !== void 0 ? all.find((t) => t.id === typeId.slice(cut + 2) && t.subsystem === qualifier) ?? null : loadTypeSpec(typeId);
|
|
41644
|
+
if (!type) throw new WaironError(`type-missing: no type has the id "${typeId}".`);
|
|
41645
|
+
if (type.id.includes("::")) {
|
|
41646
|
+
throw new WaironError(`chained-type: "${type.id}" lives in another project; rename its field from that project's own root.`);
|
|
41647
|
+
}
|
|
41648
|
+
const fields = type.fields ?? [];
|
|
41649
|
+
const moving = fields.find((f) => f.name === field);
|
|
41650
|
+
if (!moving) {
|
|
41651
|
+
throw new WaironError(`field-missing: type "${type.id}" declares no field "${field}"${fields.length ? ` (it declares ${fields.map((f) => f.name).join(", ")})` : ""}.`);
|
|
41652
|
+
}
|
|
41653
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(newName)) {
|
|
41654
|
+
throw new WaironError(`invalid-name: "${newName}" is not an identifier (a letter or underscore, then letters, digits or underscores).`);
|
|
41655
|
+
}
|
|
41656
|
+
const taken = fields.find((f) => f !== moving && f.name === newName);
|
|
41657
|
+
if (taken) throw new WaironError(`name-taken: type "${type.id}" already has a field "${newName}".`);
|
|
41658
|
+
const retired = fields.find((f) => f !== moving && (f.previousNames ?? []).includes(newName));
|
|
41659
|
+
if (retired) {
|
|
41660
|
+
throw new WaironError(`name-retired: field "${retired.name}" of type "${type.id}" lists "${newName}" in its previousNames \u2014 the name is retired on this type. Unsetting that previousNames releases it.`);
|
|
41661
|
+
}
|
|
41662
|
+
if (newName === field) return { type: qualifiedTypeId(type), from: field, to: newName, rewritten: [] };
|
|
41663
|
+
const others = all.filter((t) => t !== type);
|
|
41664
|
+
const namesField = (ref, owner) => {
|
|
41665
|
+
if (typeof ref !== "string") return false;
|
|
41666
|
+
const dot = ref.lastIndexOf(".");
|
|
41667
|
+
if (dot <= 0 || ref.slice(dot + 1) !== field) return false;
|
|
41668
|
+
const head2 = ref.slice(0, dot);
|
|
41669
|
+
if (!typeMatchesRef(type, head2)) return false;
|
|
41670
|
+
if (!others.some((t) => typeMatchesRef(t, head2))) return true;
|
|
41671
|
+
return (owner ?? "") === (type.subsystem ?? "");
|
|
41672
|
+
};
|
|
41673
|
+
const specsDir = aiPathsAt(getProjectRoot()).specsDir();
|
|
41674
|
+
const respell = (dryRun) => {
|
|
41675
|
+
const out = [];
|
|
41676
|
+
for (const file of listFilesRecursive(specsDir, ".yaml")) {
|
|
41677
|
+
let raw;
|
|
41678
|
+
try {
|
|
41679
|
+
raw = readYamlFile(file);
|
|
41680
|
+
} catch {
|
|
41681
|
+
continue;
|
|
41682
|
+
}
|
|
41683
|
+
if (!raw || typeof raw !== "object" || specKind(raw) !== "type") continue;
|
|
41684
|
+
if (raw.id === type.id && (raw.subsystem ?? "") === (type.subsystem ?? "")) continue;
|
|
41685
|
+
const owner = typeof raw.subsystem === "string" ? raw.subsystem : void 0;
|
|
41686
|
+
let changed = false;
|
|
41687
|
+
for (const f of Array.isArray(raw.fields) ? raw.fields : []) {
|
|
41688
|
+
if (!namesField(f?.references, owner)) continue;
|
|
41689
|
+
f.references = `${f.references.slice(0, f.references.lastIndexOf("."))}.${newName}`;
|
|
41690
|
+
changed = true;
|
|
41691
|
+
}
|
|
41692
|
+
if (!changed) continue;
|
|
41693
|
+
if (!dryRun) writeYamlFile(file, raw);
|
|
41694
|
+
out.push({ kind: "type", id: String(raw.id) });
|
|
41695
|
+
}
|
|
41696
|
+
return out;
|
|
41697
|
+
};
|
|
41698
|
+
assertSpecsInReach([{ kind: "type", id: type.id }, ...respell(true)], `renaming field "${field}" of type "${typeId}"`, false);
|
|
41699
|
+
const rewritten = respell(false).map((spec) => spec.id);
|
|
41700
|
+
invalidateSpecCache();
|
|
41701
|
+
const stored = loadTypeSpecs().find((t) => t.id === type.id && (t.subsystem ?? "") === (type.subsystem ?? "")) ?? type;
|
|
41702
|
+
const renamed2 = (stored.fields ?? []).map((f) => {
|
|
41703
|
+
const self = namesField(f.references, type.subsystem) ? { references: `${f.references.slice(0, f.references.lastIndexOf("."))}.${newName}` } : {};
|
|
41704
|
+
return f.name === field ? { ...f, ...self, name: newName, previousNames: [...f.previousNames ?? [], field] } : { ...f, ...self };
|
|
41705
|
+
});
|
|
41706
|
+
saveSpec("type", { ...stored, fields: renamed2 });
|
|
41707
|
+
invalidateSpecCache();
|
|
41708
|
+
return { type: qualifiedTypeId(type), from: field, to: newName, rewritten };
|
|
41709
|
+
}
|
|
40544
41710
|
function typeFileOf(id, type) {
|
|
40545
41711
|
return getTypePath(id, type.subsystem, type.group);
|
|
40546
41712
|
}
|
|
@@ -41458,17 +42624,25 @@ function composeAgentBrief(agentId) {
|
|
|
41458
42624
|
domainName: "",
|
|
41459
42625
|
variantGuidance: record.variantGuidance ?? ""
|
|
41460
42626
|
});
|
|
41461
|
-
const
|
|
42627
|
+
const ownFence = codeFenceOf(record);
|
|
42628
|
+
const shared = ownFence && isSubsystemOwner(record) ? sharedProjectFiles(ownFence) : [];
|
|
42629
|
+
const codeFence = ownFence ? [...ownFence, ...shared] : ownFence;
|
|
41462
42630
|
if (codeFence) {
|
|
41463
42631
|
const shown2 = (p) => /[*?]/.test(p) || fs20.existsSync(path29.resolve(getProjectRoot(), p)) ? `- \`${p}\`` : `- \`${p}\` (planned \u2014 create it)`;
|
|
42632
|
+
const own2 = ownFence ?? [];
|
|
42633
|
+
const sharedSection = shared.length > 0 ? `
|
|
42634
|
+
Shared with the other subsystem owners (the project's setup and shared code; coordinate edits):
|
|
42635
|
+
|
|
42636
|
+
${shared.map(shown2).join("\n")}
|
|
42637
|
+
` : "";
|
|
41464
42638
|
instructions = `${instructions.trimEnd()}
|
|
41465
42639
|
|
|
41466
42640
|
## Code write fence
|
|
41467
42641
|
|
|
41468
|
-
${
|
|
42642
|
+
${own2.length > 0 ? `Write code only here (and tests beside it, as the project lays tests out):
|
|
41469
42643
|
|
|
41470
|
-
${
|
|
41471
|
-
` : "No spec names a code location yet, so no code location is declared. The spawning session should declare the planned `sourcePath` on the implementation now \u2014 it is code linkage, not part of the approval, so declaring it costs no re-lock \u2014 and that file is then this agent's fence.\n"}`;
|
|
42644
|
+
${own2.map(shown2).join("\n")}
|
|
42645
|
+
` : "No spec names a code location yet, so no code location is declared. The spawning session should declare the planned `sourcePath` on the implementation now \u2014 it is code linkage, not part of the approval, so declaring it costs no re-lock \u2014 and that file is then this agent's fence.\n"}${sharedSection}`;
|
|
41472
42646
|
}
|
|
41473
42647
|
const guidance = loadAgentOverride(agentId);
|
|
41474
42648
|
if (guidance !== null) {
|
|
@@ -41480,8 +42654,7 @@ ${guidance.trim()}
|
|
|
41480
42654
|
`;
|
|
41481
42655
|
}
|
|
41482
42656
|
const language = implementationLanguage(record);
|
|
41483
|
-
const
|
|
41484
|
-
const typeMapping = dialect ? dialect.mappingLines() : void 0;
|
|
42657
|
+
const typeMapping = (language ? typeMappingFor(language) : null) ?? void 0;
|
|
41485
42658
|
if (typeMapping) {
|
|
41486
42659
|
instructions = `${instructions.trimEnd()}
|
|
41487
42660
|
|
|
@@ -41492,6 +42665,19 @@ Contracts speak wairon's neutral type grammar; write each type in ${language} as
|
|
|
41492
42665
|
${typeMapping.map((line) => `- ${line}`).join("\n")}
|
|
41493
42666
|
`;
|
|
41494
42667
|
}
|
|
42668
|
+
const externals = externalsUsedBy(record);
|
|
42669
|
+
let readPaths = record.readPaths;
|
|
42670
|
+
if (externals.length > 0) {
|
|
42671
|
+
instructions = `${instructions.trimEnd()}
|
|
42672
|
+
|
|
42673
|
+
## Externals used
|
|
42674
|
+
|
|
42675
|
+
These components reach other projects through \`alias::name\`; code against the pinned snapshot, never the producer's source:
|
|
42676
|
+
|
|
42677
|
+
${externals.map(describeExternal).join("\n")}
|
|
42678
|
+
`;
|
|
42679
|
+
readPaths = [.../* @__PURE__ */ new Set([...record.readPaths, ...externals.map((e) => e.pin)])];
|
|
42680
|
+
}
|
|
41495
42681
|
const profile = deriveExecutionProfile(record);
|
|
41496
42682
|
const budget = resolveBudget(profile, config.execution, record.id);
|
|
41497
42683
|
return {
|
|
@@ -41500,7 +42686,7 @@ ${typeMapping.map((line) => `- ${line}`).join("\n")}
|
|
|
41500
42686
|
template: record.template,
|
|
41501
42687
|
domainRoot: record.domainRoot,
|
|
41502
42688
|
ownedPaths: record.ownedPaths,
|
|
41503
|
-
readPaths
|
|
42689
|
+
readPaths,
|
|
41504
42690
|
instructions,
|
|
41505
42691
|
variantGuidance: record.variantGuidance || void 0,
|
|
41506
42692
|
...typeMapping ? { typeMapping } : {},
|
|
@@ -41520,8 +42706,8 @@ function codeLocationsOf(impl) {
|
|
|
41520
42706
|
return files;
|
|
41521
42707
|
}
|
|
41522
42708
|
function codeFenceOf(record) {
|
|
41523
|
-
const
|
|
41524
|
-
if (!
|
|
42709
|
+
const implementsComponents2 = record.template === "implementer" || record.template === "domain-owner" && record.creationReason.startsWith("Automatically inferred from L1");
|
|
42710
|
+
if (!implementsComponents2) return null;
|
|
41525
42711
|
const files = record.ownedPaths.filter((p) => !p.startsWith(".wai/") && !p.includes("/.wai/") && !/[*?]/.test(p));
|
|
41526
42712
|
if (files.length === 0) return [];
|
|
41527
42713
|
const dirs = files.map((f) => path29.posix.dirname(f.replace(/\\/g, "/")));
|
|
@@ -41543,7 +42729,7 @@ function implementationLanguage(record) {
|
|
|
41543
42729
|
for (const impl of implementations) {
|
|
41544
42730
|
for (const technology of impl.technologies ?? []) {
|
|
41545
42731
|
const name = typeof technology === "string" ? technology : technology.name;
|
|
41546
|
-
if (
|
|
42732
|
+
if (typeMappingFor(name)) return name.toLowerCase();
|
|
41547
42733
|
}
|
|
41548
42734
|
}
|
|
41549
42735
|
for (const impl of implementations) {
|
|
@@ -41556,7 +42742,13 @@ function implementationLanguage(record) {
|
|
|
41556
42742
|
const language = languageOfSourcePath(file);
|
|
41557
42743
|
if (language) return language;
|
|
41558
42744
|
}
|
|
41559
|
-
return void 0;
|
|
42745
|
+
if (!implementsComponents(record)) return void 0;
|
|
42746
|
+
const subsystem = loadSubsystemSpecs().find((s) => record.ownedPaths.some((p) => p.includes(`/${s.id}/`) || p.endsWith(`/${s.id}.yaml`)));
|
|
42747
|
+
const declared = subsystem?.targetLanguage ?? loadSystemSpec()?.targetLanguage;
|
|
42748
|
+
return declared ? declared.toLowerCase() : void 0;
|
|
42749
|
+
}
|
|
42750
|
+
function implementsComponents(record) {
|
|
42751
|
+
return record.template === "implementer" || isSubsystemOwner(record);
|
|
41560
42752
|
}
|
|
41561
42753
|
function memberHop(agentId) {
|
|
41562
42754
|
const at = agentId.indexOf("::");
|
|
@@ -41587,6 +42779,103 @@ function loadRegistry() {
|
|
|
41587
42779
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41588
42780
|
};
|
|
41589
42781
|
}
|
|
42782
|
+
function isSubsystemOwner(record) {
|
|
42783
|
+
return record.template === "domain-owner" && record.creationReason.startsWith("Automatically inferred from L1");
|
|
42784
|
+
}
|
|
42785
|
+
var SHARED_SETUP_FILES = [
|
|
42786
|
+
"package.json",
|
|
42787
|
+
"package-lock.json",
|
|
42788
|
+
"tsconfig.json",
|
|
42789
|
+
"Cargo.toml",
|
|
42790
|
+
"Cargo.lock",
|
|
42791
|
+
"pyproject.toml",
|
|
42792
|
+
"setup.py",
|
|
42793
|
+
"setup.cfg",
|
|
42794
|
+
"requirements.txt",
|
|
42795
|
+
"go.mod"
|
|
42796
|
+
];
|
|
42797
|
+
var SHARED_ROOT_FILES = ["src/lib.rs", "src/main.rs", "src/index.ts", "src/__init__.py"];
|
|
42798
|
+
function sharedProjectFiles(fence) {
|
|
42799
|
+
const root = getProjectRoot();
|
|
42800
|
+
const claimed = new Set(fence);
|
|
42801
|
+
for (const impl of loadImplementationSpecs()) for (const file of implementationSourceFiles(impl)) claimed.add(file);
|
|
42802
|
+
const out = [];
|
|
42803
|
+
const add2 = (file) => {
|
|
42804
|
+
if (!claimed.has(file) && !out.includes(file)) out.push(file);
|
|
42805
|
+
};
|
|
42806
|
+
for (const file of [...SHARED_SETUP_FILES, ...SHARED_ROOT_FILES]) {
|
|
42807
|
+
if (fs20.existsSync(path29.join(root, file))) add2(file);
|
|
42808
|
+
}
|
|
42809
|
+
for (const type of loadTypeSpecs()) {
|
|
42810
|
+
if (type.subsystem) continue;
|
|
42811
|
+
for (const file of typeSourceFiles(type)) add2(file);
|
|
42812
|
+
}
|
|
42813
|
+
return out;
|
|
42814
|
+
}
|
|
42815
|
+
function implementedComponents(record) {
|
|
42816
|
+
const owned = new Set(record.ownedPaths);
|
|
42817
|
+
const components = loadComponentSpecs();
|
|
42818
|
+
const contractOwner = new Map(loadInterfaceSpecs().map((i) => [i.id, i.component]));
|
|
42819
|
+
const ids = /* @__PURE__ */ new Set();
|
|
42820
|
+
for (const impl of loadImplementationSpecs()) {
|
|
42821
|
+
if (!implementationSourceFiles(impl).some((file) => owned.has(file))) continue;
|
|
42822
|
+
const component = contractOwner.get(impl.contract);
|
|
42823
|
+
if (component) ids.add(component);
|
|
42824
|
+
}
|
|
42825
|
+
if (record.id.endsWith("-implementer")) ids.add(record.id.slice(0, -"-implementer".length));
|
|
42826
|
+
return components.filter((c) => ids.has(c.id));
|
|
42827
|
+
}
|
|
42828
|
+
function externalsUsedBy(record) {
|
|
42829
|
+
const components = implementedComponents(record);
|
|
42830
|
+
if (components.length === 0) return [];
|
|
42831
|
+
const ids = new Set(components.map((c) => c.id));
|
|
42832
|
+
const refs = [];
|
|
42833
|
+
for (const c of components) refs.push(...c.dependsOn, ...c.owns ?? []);
|
|
42834
|
+
const contracts = loadInterfaceSpecs().filter((i) => ids.has(i.component));
|
|
42835
|
+
for (const contract of contracts) if (contract.implements) refs.push(contract.implements);
|
|
42836
|
+
const contractIds = new Set(contracts.map((i) => i.id));
|
|
42837
|
+
for (const impl of loadImplementationSpecs()) {
|
|
42838
|
+
if (!contractIds.has(impl.contract)) continue;
|
|
42839
|
+
for (const method of impl.methods) {
|
|
42840
|
+
for (const step of method.narrative) if (step.targetComponent) refs.push(step.targetComponent);
|
|
42841
|
+
for (const call of method.calls ?? []) refs.push(call.slice(0, call.lastIndexOf(".")));
|
|
42842
|
+
}
|
|
42843
|
+
}
|
|
42844
|
+
const byAlias = /* @__PURE__ */ new Map();
|
|
42845
|
+
for (const ref of refs) {
|
|
42846
|
+
const cut = ref.indexOf("::");
|
|
42847
|
+
if (cut <= 0) continue;
|
|
42848
|
+
const alias = ref.slice(0, cut);
|
|
42849
|
+
const names = byAlias.get(alias) ?? /* @__PURE__ */ new Set();
|
|
42850
|
+
names.add(ref.slice(cut + 2));
|
|
42851
|
+
byAlias.set(alias, names);
|
|
42852
|
+
}
|
|
42853
|
+
const root = getProjectRoot();
|
|
42854
|
+
const out = [];
|
|
42855
|
+
for (const [alias, names] of [...byAlias].sort(([a], [b]) => a.localeCompare(b))) {
|
|
42856
|
+
const pin2 = `.wai/externals/${alias}.yaml`;
|
|
42857
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
42858
|
+
let pinned = false;
|
|
42859
|
+
try {
|
|
42860
|
+
const doc = readYamlFile(path29.join(root, pin2));
|
|
42861
|
+
pinned = !!doc;
|
|
42862
|
+
for (const entry of doc?.interfaces ?? []) {
|
|
42863
|
+
if (entry.id && names.has(entry.id)) bindings.set(entry.id, { transport: entry.transport, abi: entry.abi });
|
|
42864
|
+
}
|
|
42865
|
+
} catch {
|
|
42866
|
+
}
|
|
42867
|
+
out.push({ alias, names: [...names].sort(), pin: pin2, pinned, bindings });
|
|
42868
|
+
}
|
|
42869
|
+
return out;
|
|
42870
|
+
}
|
|
42871
|
+
function describeExternal(use) {
|
|
42872
|
+
const names = use.names.map((name) => {
|
|
42873
|
+
const binding = use.bindings.get(name);
|
|
42874
|
+
const how = [binding?.transport ? `transport ${binding.transport}` : "", binding?.abi ? `abi ${binding.abi}` : ""].filter(Boolean).join(", ");
|
|
42875
|
+
return `\`${use.alias}::${name}\`${how ? ` (${how})` : ""}`;
|
|
42876
|
+
});
|
|
42877
|
+
return `- **${use.alias}** \u2014 uses ${names.join(", ")}; pinned snapshot \`${use.pin}\`${use.pinned ? "" : " (not pinned yet \u2014 run `wairon externals pin` before coding against it)"}`;
|
|
42878
|
+
}
|
|
41590
42879
|
|
|
41591
42880
|
// src/exporters/generate.ts
|
|
41592
42881
|
var path33 = __toESM(require("path"));
|
|
@@ -42432,10 +43721,10 @@ function migrate(apply3) {
|
|
|
42432
43721
|
rewrite(id, "retired-allow", `allow of ${a.code}${at} removed`, `${a.code} is retired with the rule that reported it.`);
|
|
42433
43722
|
continue;
|
|
42434
43723
|
}
|
|
42435
|
-
const
|
|
42436
|
-
if (
|
|
42437
|
-
rewrite(id, "retired-allow", `allow of ${a.code}${at} rekeyed to ${
|
|
42438
|
-
kept.push({ ...a, code:
|
|
43724
|
+
const renamed2 = RENAMED_CODES[a.code];
|
|
43725
|
+
if (renamed2 !== void 0) {
|
|
43726
|
+
rewrite(id, "retired-allow", `allow of ${a.code}${at} rekeyed to ${renamed2}`, `${a.code} is renamed ${renamed2}.`);
|
|
43727
|
+
kept.push({ ...a, code: renamed2 });
|
|
42439
43728
|
continue;
|
|
42440
43729
|
}
|
|
42441
43730
|
kept.push(a);
|
|
@@ -42485,23 +43774,59 @@ function designDigest(kind, content) {
|
|
|
42485
43774
|
const key2 = `${kind ?? ""}\0${content}`;
|
|
42486
43775
|
const memo2 = designDigestMemo.get(key2);
|
|
42487
43776
|
if (memo2 !== void 0) return memo2;
|
|
43777
|
+
const parsed = parsedSpec(content);
|
|
43778
|
+
if (!parsed) return digest(content);
|
|
43779
|
+
const out = viewDigest(designViewOf(kind, parsed));
|
|
43780
|
+
if (designDigestMemo.size > 2e4) designDigestMemo.clear();
|
|
43781
|
+
designDigestMemo.set(key2, out);
|
|
43782
|
+
return out;
|
|
43783
|
+
}
|
|
43784
|
+
var STATUS_CANDIDATES = [void 0, "draft", "design", "complete"];
|
|
43785
|
+
function designViewOf(kind, parsed) {
|
|
43786
|
+
switch (kind) {
|
|
43787
|
+
case "implementation":
|
|
43788
|
+
return implementationDesignView(parsed);
|
|
43789
|
+
case "type":
|
|
43790
|
+
return typeDesignView(parsed);
|
|
43791
|
+
case "component":
|
|
43792
|
+
return componentDesignView(parsed);
|
|
43793
|
+
case "subsystem":
|
|
43794
|
+
return subsystemDesignView(parsed);
|
|
43795
|
+
case "interface":
|
|
43796
|
+
return interfaceDesignView(parsed);
|
|
43797
|
+
default:
|
|
43798
|
+
return parsed;
|
|
43799
|
+
}
|
|
43800
|
+
}
|
|
43801
|
+
function parsedSpec(content) {
|
|
42488
43802
|
let parsed;
|
|
42489
43803
|
try {
|
|
42490
43804
|
parsed = parseYaml(content);
|
|
42491
43805
|
} catch {
|
|
42492
|
-
return
|
|
43806
|
+
return null;
|
|
42493
43807
|
}
|
|
42494
|
-
|
|
42495
|
-
|
|
42496
|
-
|
|
42497
|
-
|
|
42498
|
-
|
|
42499
|
-
|
|
43808
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
43809
|
+
}
|
|
43810
|
+
function viewDigest(view3) {
|
|
43811
|
+
return crypto7.createHash("sha256").update(canonicalize(view3), "utf8").digest("hex");
|
|
43812
|
+
}
|
|
43813
|
+
function statusApprovedIn(kind, content, recorded) {
|
|
43814
|
+
const parsed = parsedSpec(content);
|
|
43815
|
+
if (!parsed) return void 0;
|
|
43816
|
+
const view3 = designViewOf(kind, parsed);
|
|
43817
|
+
for (const status of STATUS_CANDIDATES) {
|
|
43818
|
+
const candidate = status === void 0 ? view3 : { ...view3, status };
|
|
43819
|
+
if (viewDigest(candidate) === recorded) return status ?? null;
|
|
43820
|
+
}
|
|
43821
|
+
return void 0;
|
|
43822
|
+
}
|
|
43823
|
+
function designMatches(kind, content, current2, recorded) {
|
|
43824
|
+
return current2 === recorded || statusApprovedIn(kind, content, recorded) !== void 0;
|
|
42500
43825
|
}
|
|
42501
43826
|
function linkedKindsByFile() {
|
|
42502
43827
|
const paths = scanAllSpecs().paths;
|
|
42503
43828
|
const kinds = /* @__PURE__ */ new Map();
|
|
42504
|
-
for (const kind of ["implementation", "type", "component"]) {
|
|
43829
|
+
for (const kind of ["implementation", "type", "component", "subsystem", "interface"]) {
|
|
42505
43830
|
for (const file of Object.values(paths[kind])) kinds.set(path35.resolve(file), kind);
|
|
42506
43831
|
}
|
|
42507
43832
|
return kinds;
|
|
@@ -42560,15 +43885,24 @@ function diffAgainstApproval(root = getProjectRoot()) {
|
|
|
42560
43885
|
const record = approvalRecord(root);
|
|
42561
43886
|
const approved = record?.specs ?? null;
|
|
42562
43887
|
if (!approved) return null;
|
|
42563
|
-
const
|
|
43888
|
+
const reading = readingOf(record);
|
|
43889
|
+
const files = new Map(ownSpecFiles2(root).map((f) => [f.key, f]));
|
|
43890
|
+
const current2 = {};
|
|
43891
|
+
for (const [key2, file] of files) current2[key2] = digestIn(reading, file.kind, file.content);
|
|
42564
43892
|
const added = [];
|
|
42565
43893
|
const changed = [];
|
|
42566
43894
|
const removed = [];
|
|
42567
43895
|
const unchangedPaths = [];
|
|
43896
|
+
const same = (rel2, before, now) => {
|
|
43897
|
+
if (before === now) return true;
|
|
43898
|
+
if (reading !== "design") return false;
|
|
43899
|
+
const file = files.get(rel2);
|
|
43900
|
+
return designMatches(file.kind, file.content, now, before);
|
|
43901
|
+
};
|
|
42568
43902
|
for (const [rel2, d] of Object.entries(current2)) {
|
|
42569
43903
|
const before = approved[rel2];
|
|
42570
43904
|
if (before === void 0) added.push(rel2);
|
|
42571
|
-
else if (before
|
|
43905
|
+
else if (!same(rel2, before, d)) changed.push(rel2);
|
|
42572
43906
|
else unchangedPaths.push(rel2);
|
|
42573
43907
|
}
|
|
42574
43908
|
for (const rel2 of Object.keys(approved)) {
|
|
@@ -42591,23 +43925,38 @@ function settledSpecPaths(root = getProjectRoot()) {
|
|
|
42591
43925
|
var REEXPRESSED_BY = "wairon doctor --fix";
|
|
42592
43926
|
function reexpress(gate) {
|
|
42593
43927
|
const record = readLockRecord();
|
|
43928
|
+
if (!record) return null;
|
|
43929
|
+
const fromReading = readingOf(record);
|
|
42594
43930
|
const asRecorded = gate.asRecorded;
|
|
42595
|
-
const holds = !!asRecorded && record
|
|
42596
|
-
|
|
42597
|
-
|
|
42598
|
-
|
|
42599
|
-
|
|
42600
|
-
|
|
42601
|
-
|
|
42602
|
-
|
|
42603
|
-
|
|
42604
|
-
|
|
42605
|
-
|
|
42606
|
-
|
|
42607
|
-
|
|
42608
|
-
|
|
42609
|
-
|
|
42610
|
-
};
|
|
43931
|
+
const holds = !!asRecorded && record.stateId.algorithm === asRecorded.algorithm && record.stateId.digest === asRecorded.digest;
|
|
43932
|
+
const reexpressed = { at: (/* @__PURE__ */ new Date()).toISOString(), fromAlgorithm: record.stateId.algorithm, fromReading, by: REEXPRESSED_BY };
|
|
43933
|
+
if (holds) {
|
|
43934
|
+
return {
|
|
43935
|
+
...record,
|
|
43936
|
+
stateId: { algorithm: gate.algorithm, digest: gate.digest },
|
|
43937
|
+
specs: captureApprovedSpecs(),
|
|
43938
|
+
specsReading: "design",
|
|
43939
|
+
format: 3,
|
|
43940
|
+
reexpressed
|
|
43941
|
+
};
|
|
43942
|
+
}
|
|
43943
|
+
if (fromReading !== "content" || !record.specs) return null;
|
|
43944
|
+
const diff2 = diffAgainstApproval();
|
|
43945
|
+
if (!diff2 || diffSize(diff2) > 0) return null;
|
|
43946
|
+
return { ...record, specs: captureApprovedSpecs(), specsReading: "design", format: 3, reexpressed };
|
|
43947
|
+
}
|
|
43948
|
+
function approvedStatuses(record, root = getProjectRoot()) {
|
|
43949
|
+
const out = /* @__PURE__ */ new Map();
|
|
43950
|
+
if (record.specsReading !== "design" || !record.specs) return out;
|
|
43951
|
+
for (const file of ownSpecFiles2(root)) {
|
|
43952
|
+
const recorded = record.specs[file.key];
|
|
43953
|
+
if (recorded === void 0 || !file.kind) continue;
|
|
43954
|
+
const approved = statusApprovedIn(file.kind, file.content, recorded);
|
|
43955
|
+
if (approved === void 0) continue;
|
|
43956
|
+
const id = parsedSpec(file.content)?.id;
|
|
43957
|
+
if (typeof id === "string") out.set(`${file.kind}:${id}`, approved ?? "complete");
|
|
43958
|
+
}
|
|
43959
|
+
return out;
|
|
42611
43960
|
}
|
|
42612
43961
|
function approvalVerdict(approvals) {
|
|
42613
43962
|
const quiet = { text: "", drifted: false };
|
|
@@ -42615,10 +43964,10 @@ function approvalVerdict(approvals) {
|
|
|
42615
43964
|
const approval = approvalRecord();
|
|
42616
43965
|
if (!approval) return quiet;
|
|
42617
43966
|
const by = describeApprover(approval.lockedBy);
|
|
42618
|
-
const
|
|
43967
|
+
const diff2 = diffAgainstApproval();
|
|
43968
|
+
const notes = memberNotes(approvals ?? [], approval, diff2);
|
|
42619
43969
|
const pinDrift = notes.drift;
|
|
42620
43970
|
const childNote = notes.text;
|
|
42621
|
-
const diff2 = diffAgainstApproval();
|
|
42622
43971
|
if (!diff2) {
|
|
42623
43972
|
return {
|
|
42624
43973
|
text: `
|
|
@@ -42665,14 +44014,16 @@ function isContentReadingRecord(record) {
|
|
|
42665
44014
|
var REEXPRESS_HINT = "This approval predates lock format 3 and is read in its original reading; `wairon doctor --fix` carries it into the design reading without a review (the approval stays the approver's), after which adding or moving a sourcePath never drifts it.";
|
|
42666
44015
|
var FORMAT2_STALE_NOTE = "it was taken before code linkage (sourcePath, symbol, simPath and the like) left the approval, and something it covered moved since \u2014 the design, or only code linkage, which this record can no longer tell apart. One `wairon lock` clears it for good: from then on linkage never drifts the approval";
|
|
42667
44016
|
var GATE_UPGRADE_NOTE = "the gate identity gained inputs in this release (members' composition subjects, `composition`; code conformance moved beside the claim)";
|
|
42668
|
-
|
|
44017
|
+
var GATE_MOVED_NOTE = "no own spec file changed since it was taken \u2014 not even its code linkage \u2014 so what moved is the gate it was judged under: the design rules this wairon release judges by, or the project's rule tuning, `composition`, network declaration, consumed contracts or a member's approval. One `wairon lock` re-approves the unchanged design under the current gate";
|
|
44018
|
+
function memberNotes(approvals, record, diff2) {
|
|
42669
44019
|
const own2 = approvals.find((a) => a.key === "");
|
|
42670
44020
|
const direct = approvals.filter((a) => a.parent === "" && a.as !== "part");
|
|
42671
44021
|
const moved = direct.filter((a) => a.pinned === "moved");
|
|
42672
44022
|
const unapproved = direct.filter((a) => a.state !== "approved");
|
|
42673
44023
|
const lines = [];
|
|
42674
44024
|
if (own2?.upgraded) {
|
|
42675
|
-
|
|
44025
|
+
const by = record?.validatorVersion ? ` (taken by wairon ${record.validatorVersion})` : "";
|
|
44026
|
+
lines.push(diff2 && diffSize(diff2) === 0 ? `This approval no longer matches${by}: ${GATE_MOVED_NOTE}.` : record && record.format === 2 ? `This approval no longer matches: ${FORMAT2_STALE_NOTE}.` : `This approval was taken under an earlier gate identity \u2014 ${GATE_UPGRADE_NOTE}. Re-lock once.`);
|
|
42676
44027
|
}
|
|
42677
44028
|
if (moved.length) {
|
|
42678
44029
|
lines.push(`${moved.length} member pin(s) moved since approval: ${moved.map((a) => a.alias ?? a.key).join(", ")} \u2014 re-lock to approve them.`);
|
|
@@ -42774,17 +44125,17 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
|
|
|
42774
44125
|
- **Deprecated forms** (they still resolve for one release, are reported, and \`wairon doctor --fix\` rewrites them): a leading \`::\` (\`::shared::error-type\`), \`super::\` (\`super::sibling_comp\`), member paths (\`billing::invoice::invoice_portal\`), and an L1 subsystem carrying \`projectPath\` (\`DEPRECATED_MOUNT_FORM\`).
|
|
42775
44126
|
- **Do not run the \`wairon\` CLI**: Use \`sdd_validate_tree\` and \`sdd_get_status\` instead of CLI commands.
|
|
42776
44127
|
- **Handoff to implementation**: Once design is complete and validates cleanly, tell the human: *"The specs are complete and validate. Please run \`wairon lock\` to approve them, and commit \`.wai/lock.json\`."* The lock records the approval; it does not rewrite spec files or their \`status\`. No session restart is needed after the lock \u2014 delegate implementation right away via the \`sdd-delegate\` skill.
|
|
42777
|
-
- **Approval, not status**: a design is ready to implement when it is APPROVED \u2014 \`sdd_get_status\` reports the approval state (approved, or which specs changed since), and \`wairon lock-check\` gives the same verdict in CI. A spec's \`status\` (draft/design/complete) is authoring readiness only; never wait for it to become \`complete\`.
|
|
44128
|
+
- **Approval, not status**: a design is ready to implement when it is APPROVED \u2014 \`sdd_get_status\` reports the approval state (approved, or which specs changed since), and \`wairon lock-check\` gives the same verdict in CI. A spec's \`status\` (draft/design/complete) is authoring readiness only; never wait for it to become \`complete\`. It is left out of the approval, so promoting a status never reopens an approved design.
|
|
42778
44129
|
- **Code linkage is not approval**: the lock approves the DESIGN. Where it is realized \u2014 \`sourcePath\`, \`symbol\`, \`exportedVia\`, \`simPath\`, \`injectedParams\`, conformance tiers, timestamps \u2014 is outside the approved digests, so setting or changing it never asks for a re-lock. Declare each implementation's planned \`sourcePath\` at design time: a named file not written yet is \`SOURCE_FILE_PLANNED\` (a notice), at method level as at implementation level; once the component's realization begins (any file it names exists), a contract method naming no file is \`METHOD_SOURCE_PATH_MISSING\` (warning) and one its existing file does not hold is \`UNREALIZED_METHOD\`. \`rules.conformance.requireCode: true\` makes the planned and unlinked notices errors. Briefs fence planned files, marked \`(planned \u2014 create it)\`.
|
|
42779
|
-
- **Externals \u2014 the pin gates, live drift is visible**: declare a project this one consumes with \`sdd_add_external\` (alias, and a source \`../sibling\`, \`hosted:<id>\`, \`<git url>\` or \`<git url>#<commit>\`) \u2014 never by hand-editing \`.wai/project.yaml\`; it is checked against the producer and pinned. The owner's gate judges every external against its pin. \`sdd_validate_tree\` and \`sdd_get_status\` also compare each external with its LIVE producer, offline, as ADVISORY findings (\`advisory: true\`): \`EXTERNAL_LIVE_INCOMPATIBLE\` (a used member changed, was renamed \u2014 the new name is given \u2014 or is gone), \`EXTERNAL_DRIFTED\`, \`EXTERNAL_LIVE_UNCOMPARED\`. They never make the tree invalid or fail \`--ci\`; the fix is to adapt the uses, then ask the human to re-pin (\`wairon externals pin <alias>\`). A git producer is compared live only by \`wairon externals status\` \u2014 the opt-in live CI gate (exit 1 incompatible, 2 not compared, 0 otherwise).
|
|
44130
|
+
- **Externals \u2014 the pin gates, live drift is visible**: declare a project this one consumes with \`sdd_add_external\` (alias, and a source \`../sibling\`, \`hosted:<id>\`, \`<git url>\` or \`<git url>#<commit>\`) \u2014 never by hand-editing \`.wai/project.yaml\`; it is checked against the producer and pinned. Change its \`use\` imports with \`sdd_update_external\` and remove it (declaration and pin together) with \`sdd_remove_external\`. A name the producer exports to a narrower audience (\`project\` < \`department\` < \`instance\` < \`partner\` < \`external\`) than this project is read at is refused, naming both. The owner's gate judges every external against its pin. \`sdd_validate_tree\` and \`sdd_get_status\` also compare each external with its LIVE producer, offline, as ADVISORY findings (\`advisory: true\`): \`EXTERNAL_LIVE_INCOMPATIBLE\` (a used member changed, was renamed \u2014 the new name is given \u2014 or is gone), \`EXTERNAL_DRIFTED\`, \`EXTERNAL_LIVE_UNCOMPARED\`. They never make the tree invalid or fail \`--ci\`; the fix is to adapt the uses, then ask the human to re-pin (\`wairon externals pin <alias>\`). A git producer is compared live only by \`wairon externals status\` \u2014 the opt-in live CI gate (exit 1 incompatible, 2 not compared, 0 otherwise).
|
|
42780
44131
|
- **To implement code**: Delegate via the \`sdd-delegate\` skill: fetch the component's live brief with the \`sdd_get_agent_brief\` MCP tool (or the \`wairon-agent://\` resource) and spawn a subagent from it. Briefs are composed per call from the current spec tree, so they are always current \u2014 never wait for a restart. Implementations must match L3 interfaces and L5 narratives exactly. Generated agent files under \`.claude/agents/\` are an optional materialized view of the same topology \u2014 the live briefs are canonical.
|
|
42781
44132
|
|
|
42782
44133
|
### Rules (enforced by \`sdd_validate_tree\`)
|
|
42783
44134
|
1. **Design before code**: Complete spec and pass validator before writing source code.
|
|
42784
44135
|
2. **Human-in-the-loop**: Ask user approval for each spec layer before proceeding.
|
|
42785
44136
|
3. **Spec consistency**: If a 1:1 narrative match is incorrect or conflicts with L0 requirements, escalate a spec revision first. Never ship mismatched code.
|
|
42786
|
-
4. **No persistence shortcuts & strict layers**: A Portal must never depend directly on a Store, Registry, or Adapter. Portal reads MAY go through a Repository/Index facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a write-effect facade method is an error (\`PORTAL_WRITE_SHORTCUT\`) \u2014 writes always route through an Orchestrator. Held domain state always lives in a dedicated data component, never as fields inside an Orchestrator.
|
|
42787
|
-
5. **Every Portal verb is reached**: a modelled caller reaches it (a \`call\` step along \`dependsOn\`, \`alias::portal.verb\` across projects), or it is an **entry** \u2014 callers outside the design reach it: \`invokedBy: { kind: entry, caller }\` on the Portal (every verb inherits it) or on one verb. Declare an entry only for real outside callers (browsers, a CLI user, an AI tool over stdio, applications linking a library) \u2014 **never invent one to silence an \`UNUSED_*\` finding**: model the caller, or remove the verb. A Portal states one \`transport\` (\`HTTP\`, \`gRPC\`, \`GraphQL\`, \`MessageBus\`, \`CLI\`, \`IPC\`, \`NamedPipe\`, \`JSONRPC\` for stdio JSON-RPC, \`InProcess\` for a library, which binds no endpoint). A project may declare \`network\`
|
|
44137
|
+
4. **No persistence shortcuts & strict layers**: A Portal must never depend directly on a Store, Registry, or Adapter. Portal reads MAY go through a Repository/Index facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a write-effect facade method is an error (\`PORTAL_WRITE_SHORTCUT\`) \u2014 writes always route through an Orchestrator. Held domain state always lives in a dedicated data component, never as fields inside an Orchestrator. Design the SMALLEST sound shape: genuinely simple state (keyed reads and writes) is one standalone Store \u2014 the sanctioned shape, with workflow-layer consumers and a lint.allow reason on the UNOWNED_STORE warning \u2014 and the Repository pattern (a facade owning Store + Registry + one Index per real lookup) is for state with real lookup or index needs. Never split Store/Registry/Index for simple state, never combine their roles into one component, and never fold state into a consuming component because a link was refused.
|
|
44138
|
+
5. **Every Portal verb is reached**: a modelled caller reaches it (a \`call\` step along \`dependsOn\`, \`alias::portal.verb\` across projects), or it is an **entry** \u2014 callers outside the design reach it: \`invokedBy: { kind: entry, caller }\` on the Portal (every verb inherits it) or on one verb. Declare an entry only for real outside callers (browsers, a CLI user, an AI tool over stdio, applications linking a library) \u2014 **never invent one to silence an \`UNUSED_*\` finding**: model the caller, or remove the verb. A Portal states one \`transport\` (\`HTTP\`, \`gRPC\`, \`GraphQL\`, \`MessageBus\`, \`CLI\`, \`IPC\`, \`NamedPipe\`, \`JSONRPC\` for stdio JSON-RPC, \`InProcess\` for a library, which binds no endpoint). A project may declare a network (\`sdd_set_network\`; the human runs \`wairon network declare\` \u2014 never a hand edit of \`project.yaml\`): an entry's \`scope\` is then \`outside\` (default) or \`network\` (sibling services), and inside a network only a \`gateway\` Portal is entered from outside. Another project's \`InProcess\` library is called directly from any component \u2014 no client Adapter \u2014 within the purity (\`effect\`) and language-bridge (\`abi\`) checks.
|
|
42788
44139
|
6. **Naming follows the target language**: methods are named in the project's method casing (camelCase for TypeScript/Java, snake_case for Rust/Python, from \`targetLanguage\` unless \`rules.naming\` says otherwise); a wire name that differs (an HTTP path, a CLI command, a JSON-RPC method) belongs on the method's \`endpoint\`, never in the method name.
|
|
42789
44140
|
|
|
42790
44141
|
### Component Vocabulary
|
|
@@ -43073,7 +44424,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
43073
44424
|
const mark = fillDecor(decor);
|
|
43074
44425
|
scanAllSpecs({ memberDepth: options.memberDepth });
|
|
43075
44426
|
const system = loadSystemSpec();
|
|
43076
|
-
const loaderErrors = getLoaderIssues();
|
|
44427
|
+
const loaderErrors = getLoaderIssues().filter((issue2) => issue2.severity === "error");
|
|
43077
44428
|
if (loaderErrors.length > 0) {
|
|
43078
44429
|
let errText2 = "Failed to parse specification files:\n";
|
|
43079
44430
|
for (const issue2 of loaderErrors) {
|
|
@@ -43087,7 +44438,10 @@ function getStatusReport(options = {}, decor) {
|
|
|
43087
44438
|
const partLine = partOf ? `${mark.layer("system", "\u25CF Part of:")} ${mark.emphasis(partOf.project)} ${parentPinState(partOf.project)}
|
|
43088
44439
|
` : "";
|
|
43089
44440
|
if (!system && !partOf) {
|
|
43090
|
-
|
|
44441
|
+
if (!pathExists(fromProjectRoot(".wai/project.yaml"))) {
|
|
44442
|
+
return { text: `No wairon project binds this folder (no .wai/project.yaml at ${fromProjectRoot(".")}): there is no design to report.`, failed: true };
|
|
44443
|
+
}
|
|
44444
|
+
return { text: "This project has no L0 System spec yet (.wai/specs/.index.yaml): its design has not been started.", failed: true };
|
|
43091
44445
|
}
|
|
43092
44446
|
let subsystems = loadSubsystemSpecs();
|
|
43093
44447
|
let components = loadComponentSpecs();
|
|
@@ -43107,6 +44461,13 @@ function getStatusReport(options = {}, decor) {
|
|
|
43107
44461
|
}
|
|
43108
44462
|
let output = "";
|
|
43109
44463
|
const componentScores = /* @__PURE__ */ new Map();
|
|
44464
|
+
const family = graph();
|
|
44465
|
+
const ownerOf4 = (key2) => family.owners.get(key2) ?? "";
|
|
44466
|
+
const sourceExists = (specId, file) => {
|
|
44467
|
+
const owner = ownerOf4(specId);
|
|
44468
|
+
const dir = owner === "" ? void 0 : family.nodes.find((n) => n.namespace === owner)?.directory;
|
|
44469
|
+
return pathExists(dir ? path37.resolve(dir, file) : fromProjectRoot(file));
|
|
44470
|
+
};
|
|
43110
44471
|
for (const comp of components) {
|
|
43111
44472
|
let score = 20;
|
|
43112
44473
|
const intf = interfaces.find((i) => i.component === comp.id);
|
|
@@ -43117,7 +44478,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
43117
44478
|
if (impl) {
|
|
43118
44479
|
score += 30;
|
|
43119
44480
|
const sourceFiles = implementationSourceFiles(impl);
|
|
43120
|
-
if (sourceFiles.length > 0 && sourceFiles.every((f) =>
|
|
44481
|
+
if (sourceFiles.length > 0 && sourceFiles.every((f) => sourceExists(impl.id, f))) {
|
|
43121
44482
|
score += 20;
|
|
43122
44483
|
}
|
|
43123
44484
|
}
|
|
@@ -43147,13 +44508,6 @@ function getStatusReport(options = {}, decor) {
|
|
|
43147
44508
|
const systemScore = subsystems.length > 0 ? Math.round(totalSubsystemsScore / subsystems.length) : 0;
|
|
43148
44509
|
output += system ? `${mark.layer("system", "\u25CF System:")} ${mark.emphasis(system.name)} ${mark.score(systemScore, `(${systemScore}% Complete)`)}
|
|
43149
44510
|
` : partLine;
|
|
43150
|
-
const family = graph();
|
|
43151
|
-
const ownerOf4 = (key2) => family.owners.get(key2) ?? "";
|
|
43152
|
-
const sourceExists = (specId, file) => {
|
|
43153
|
-
const owner = ownerOf4(specId);
|
|
43154
|
-
const dir = owner === "" ? void 0 : family.nodes.find((n) => n.namespace === owner)?.directory;
|
|
43155
|
-
return pathExists(dir ? path37.resolve(dir, file) : fromProjectRoot(file));
|
|
43156
|
-
};
|
|
43157
44511
|
const holdsShown = (ns) => subsystems.some((s) => ownerOf4(s.id) === ns) || family.nodes.some((n) => n.parent === ns && n.namespace !== ns && holdsShown(n.namespace));
|
|
43158
44512
|
const partMark = /* @__PURE__ */ new Map();
|
|
43159
44513
|
for (const node of family.nodes) {
|
|
@@ -43194,7 +44548,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
43194
44548
|
const methodsWithOwnPath = impl.methods.filter((m) => m.sourcePath);
|
|
43195
44549
|
let pathStr;
|
|
43196
44550
|
if (impl.sourcePath) {
|
|
43197
|
-
pathStr = sourceExists(impl.id, impl.sourcePath) ? mark.present(` -> ${impl.sourcePath}`) : mark.
|
|
44551
|
+
pathStr = sourceExists(impl.id, impl.sourcePath) ? mark.present(` -> ${impl.sourcePath}`) : mark.draft(` -> ${impl.sourcePath} (planned \u2014 not written yet)`);
|
|
43198
44552
|
} else if (methodsWithOwnPath.length > 0) {
|
|
43199
44553
|
pathStr = "";
|
|
43200
44554
|
} else {
|
|
@@ -43208,7 +44562,7 @@ function getStatusReport(options = {}, decor) {
|
|
|
43208
44562
|
const isLastMethod = k === methodsWithOwnPath.length - 1;
|
|
43209
44563
|
const methodPrefix = isLastMethod ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
43210
44564
|
const methodPath = method.sourcePath;
|
|
43211
|
-
const methodLine = sourceExists(impl.id, methodPath) ? mark.present(`method ${method.name} -> ${methodPath}`) : mark.
|
|
44565
|
+
const methodLine = sourceExists(impl.id, methodPath) ? mark.present(`method ${method.name} -> ${methodPath}`) : mark.draft(`method ${method.name} -> ${methodPath} (planned \u2014 not written yet)`);
|
|
43212
44566
|
output += `${mark.structure(methodIndent + methodPrefix)}${methodLine}
|
|
43213
44567
|
`;
|
|
43214
44568
|
}
|
|
@@ -43816,6 +45170,15 @@ function carryInto(restatement, existing, candidate) {
|
|
|
43816
45170
|
carried.push(`previousNames (${String(method.name)})`);
|
|
43817
45171
|
}
|
|
43818
45172
|
}
|
|
45173
|
+
if (restatement.kind === "type" && Array.isArray(candidate.fields)) {
|
|
45174
|
+
const stored = existing.fields ?? [];
|
|
45175
|
+
for (const field of candidate.fields) {
|
|
45176
|
+
const prev = stored.find((p) => p.name === field.name);
|
|
45177
|
+
if (prev?.previousNames === void 0 || field.previousNames !== void 0) continue;
|
|
45178
|
+
field.previousNames = prev.previousNames;
|
|
45179
|
+
carried.push(`previousNames (field ${String(field.name)})`);
|
|
45180
|
+
}
|
|
45181
|
+
}
|
|
43819
45182
|
carried.push(...carryUnexpressed(existing, candidate, [...restatement.fields, "createdAt"]));
|
|
43820
45183
|
return carried;
|
|
43821
45184
|
}
|
|
@@ -44133,7 +45496,7 @@ function buildServerInstructions3() {
|
|
|
44133
45496
|
}
|
|
44134
45497
|
|
|
44135
45498
|
// src/network/adapters/files.ts
|
|
44136
|
-
var
|
|
45499
|
+
var import_yaml15 = require("yaml");
|
|
44137
45500
|
|
|
44138
45501
|
// src/network/types.ts
|
|
44139
45502
|
function workload(party) {
|
|
@@ -44214,6 +45577,23 @@ function addFlow(flows3, flow) {
|
|
|
44214
45577
|
function byCalleeThenCaller(a, b) {
|
|
44215
45578
|
return (a.to.project ?? "").localeCompare(b.to.project ?? "") || key(a.to).localeCompare(key(b.to)) || key(a.from).localeCompare(key(b.from));
|
|
44216
45579
|
}
|
|
45580
|
+
function findingLine(f) {
|
|
45581
|
+
return `${f.code}: ${f.message}`;
|
|
45582
|
+
}
|
|
45583
|
+
function markFlows(flows3, f) {
|
|
45584
|
+
if (f.specId === void 0) return;
|
|
45585
|
+
const onVerb = (flow) => flow.from.scope !== void 0 && flow.to.component === f.specId && (f.at === void 0 || flow.to.verb === f.at);
|
|
45586
|
+
const onCall = (flow) => flow.from.scope === void 0 && flow.from.component === f.specId && f.at !== void 0 && flow.evidence.includes(`call ${f.at}`);
|
|
45587
|
+
for (const flow of flows3.filter((x) => onVerb(x) || onCall(x))) {
|
|
45588
|
+
const field = f.severity === "error" ? "refusedBy" : "flaggedBy";
|
|
45589
|
+
const list = flow[field] ?? [];
|
|
45590
|
+
if (!list.includes(findingLine(f))) list.push(findingLine(f));
|
|
45591
|
+
flow[field] = list;
|
|
45592
|
+
}
|
|
45593
|
+
}
|
|
45594
|
+
function isRefused(flow) {
|
|
45595
|
+
return (flow.refusedBy?.length ?? 0) > 0;
|
|
45596
|
+
}
|
|
44217
45597
|
function project2(model) {
|
|
44218
45598
|
const flows3 = /* @__PURE__ */ new Map();
|
|
44219
45599
|
for (const verb of model.verbs.filter((v) => transportKind(v.transport) === "network")) {
|
|
@@ -44226,7 +45606,9 @@ function project2(model) {
|
|
|
44226
45606
|
}
|
|
44227
45607
|
addFlow(flows3, outsideFlow(model, verb));
|
|
44228
45608
|
}
|
|
44229
|
-
|
|
45609
|
+
const marked = [...flows3.values()];
|
|
45610
|
+
for (const f of model.findings ?? []) markFlows(marked, f);
|
|
45611
|
+
return marked.sort(byCalleeThenCaller);
|
|
44230
45612
|
}
|
|
44231
45613
|
function networkName2(id) {
|
|
44232
45614
|
return id === "" ? "(root network)" : id;
|
|
@@ -44236,24 +45618,89 @@ function chainOf(flow) {
|
|
|
44236
45618
|
for (const network of flow.crosses) lines.push(`enters network ${networkName2(network)}`);
|
|
44237
45619
|
if (flow.via !== void 0) lines.push(`through gateway ${flow.via}`);
|
|
44238
45620
|
lines.push(`reaches ${key(flow.to)} over ${flow.transport}${flow.binding ? ` (${flow.binding})` : ""}`);
|
|
45621
|
+
for (const flag of flow.flaggedBy ?? []) lines.push(`marked by the gate: ${flag}`);
|
|
44239
45622
|
return lines;
|
|
44240
45623
|
}
|
|
44241
|
-
function
|
|
44242
|
-
|
|
45624
|
+
function verbParty(v) {
|
|
45625
|
+
return { project: v.project, ...v.subsystem !== void 0 ? { subsystem: v.subsystem } : {}, component: v.portal, verb: v.verb };
|
|
45626
|
+
}
|
|
45627
|
+
function reachesOverNetwork(model, to) {
|
|
45628
|
+
return model.verbs.some((v) => transportKind(v.transport) === "network" && isNamed(verbParty(v), to));
|
|
45629
|
+
}
|
|
45630
|
+
function isKnown(model, name) {
|
|
45631
|
+
if (name === "outside" || name === "network") return true;
|
|
45632
|
+
if (name.startsWith("network:")) return model.networks.some((n) => n.id === name.slice("network:".length));
|
|
45633
|
+
const placed = (model.placements ?? []).some((p) => isNamed({ project: p.project, ...p.subsystem !== void 0 ? { subsystem: p.subsystem } : {}, component: p.component }, name));
|
|
45634
|
+
return placed || model.verbs.some((v) => isNamed(verbParty(v), name)) || model.calls.some((c) => isNamed(callerOf(c), name));
|
|
45635
|
+
}
|
|
45636
|
+
function notAllowed(model, flows3, refused3, from, to) {
|
|
45637
|
+
if (refused3.length > 0) {
|
|
45638
|
+
const refusedBy = [...new Set(refused3.flatMap((f) => f.refusedBy ?? []))];
|
|
45639
|
+
const chain2 = [`the design names ${refused3.length === 1 ? "a flow" : `${refused3.length} flows`} from ${from} to ${to}, but the gate refuses ${refused3.length === 1 ? "it" : "them"}: nothing admits it until the design passes \`wairon validate\``];
|
|
45640
|
+
for (const f of refused3) chain2.push(`refused: ${key(f.from)} -> ${key(f.to)} over ${f.transport}${f.binding ? ` (${f.binding})` : ""}`);
|
|
45641
|
+
for (const line of refusedBy) chain2.push(` ${line}`);
|
|
45642
|
+
return { allowed: false, flows: [], chain: chain2, refusedBy };
|
|
45643
|
+
}
|
|
45644
|
+
if (!reachesOverNetwork(model, to)) {
|
|
45645
|
+
return {
|
|
45646
|
+
allowed: false,
|
|
45647
|
+
flows: [],
|
|
45648
|
+
inProcess: true,
|
|
45649
|
+
chain: [
|
|
45650
|
+
`${to} takes no network flow: it is reached in-process (a direct call, or a local transport, inside one process), so the design neither allows nor forbids a network flow to it`,
|
|
45651
|
+
"network flows are made only by verbs of Portals with a network transport (HTTP, gRPC, GraphQL, MessageBus, Custom)"
|
|
45652
|
+
]
|
|
45653
|
+
};
|
|
45654
|
+
}
|
|
45655
|
+
const nearest = flows3.filter((f) => isNamed(f.to, to) && !isRefused(f)).slice(0, 10);
|
|
44243
45656
|
const chain = [`nothing in the design allows ${from} to reach ${to}`];
|
|
44244
45657
|
if (nearest.length === 0) chain.push(`no flow reaches ${to} at all`);
|
|
44245
45658
|
for (const f of nearest) chain.push(`allowed instead: ${key(f.from)} -> ${key(f.to)} over ${f.transport}`);
|
|
44246
45659
|
return { allowed: false, flows: [], chain };
|
|
44247
45660
|
}
|
|
44248
|
-
function explain(flows3, from, to) {
|
|
45661
|
+
function explain(flows3, from, to, model) {
|
|
45662
|
+
const unknown = [from, to].filter((n) => !isKnown(model, n));
|
|
45663
|
+
if (unknown.length > 0) {
|
|
45664
|
+
return {
|
|
45665
|
+
allowed: false,
|
|
45666
|
+
flows: [],
|
|
45667
|
+
unknown,
|
|
45668
|
+
chain: [
|
|
45669
|
+
...unknown.map((n) => `unknown party "${n}": nothing in the design is named so`),
|
|
45670
|
+
"name a party as outside, network[:<id>], a project, a subsystem or a component, and a callee also as portal.verb; a subsystem of this project is named bare (a project is reached as <alias>::<name>)"
|
|
45671
|
+
]
|
|
45672
|
+
};
|
|
45673
|
+
}
|
|
44249
45674
|
const selected = flows3.filter((f) => isNamed(f.from, from) && isNamed(f.to, to));
|
|
44250
|
-
|
|
44251
|
-
|
|
44252
|
-
|
|
45675
|
+
const admitted = selected.filter((f) => !isRefused(f));
|
|
45676
|
+
if (admitted.length === 0) return notAllowed(model, flows3, selected.filter(isRefused), from, to);
|
|
45677
|
+
const chain = admitted.flatMap((f, i) => [...admitted.length > 1 ? [`flow ${i + 1}:`] : [], ...chainOf(f)]);
|
|
45678
|
+
return { allowed: true, flows: admitted, chain };
|
|
44253
45679
|
}
|
|
44254
45680
|
|
|
44255
45681
|
// src/network/codec.ts
|
|
44256
|
-
var COLUMNS = ["from", "to", "transport", "binding", "crosses", "via", "evidence"];
|
|
45682
|
+
var COLUMNS = ["from", "to", "transport", "binding", "crosses", "via", "evidence", "gate"];
|
|
45683
|
+
function refusedFlow(flow) {
|
|
45684
|
+
return (flow.refusedBy?.length ?? 0) > 0;
|
|
45685
|
+
}
|
|
45686
|
+
function codeOf(line) {
|
|
45687
|
+
const cut = line.indexOf(":");
|
|
45688
|
+
return cut === -1 ? line : line.slice(0, cut);
|
|
45689
|
+
}
|
|
45690
|
+
function gateCell(flow) {
|
|
45691
|
+
if (refusedFlow(flow)) return `REFUSED: ${[...new Set(flow.refusedBy.map(codeOf))].join("; ")}`;
|
|
45692
|
+
return [...new Set((flow.flaggedBy ?? []).map(codeOf))].join("; ");
|
|
45693
|
+
}
|
|
45694
|
+
function gateFindingsOf(flows3) {
|
|
45695
|
+
const errors = /* @__PURE__ */ new Set();
|
|
45696
|
+
const flags = /* @__PURE__ */ new Set();
|
|
45697
|
+
for (const f of flows3) {
|
|
45698
|
+
for (const line of f.refusedBy ?? []) errors.add(line);
|
|
45699
|
+
for (const line of f.flaggedBy ?? []) flags.add(line);
|
|
45700
|
+
}
|
|
45701
|
+
const withSeverity = (line, severity) => `${codeOf(line)} (${severity})${line.slice(codeOf(line).length)}`;
|
|
45702
|
+
return [...[...errors].map((l) => withSeverity(l, "error")), ...[...flags].map((l) => withSeverity(l, "warning"))];
|
|
45703
|
+
}
|
|
44257
45704
|
function networkName3(id) {
|
|
44258
45705
|
return id === "" ? "(root network)" : id;
|
|
44259
45706
|
}
|
|
@@ -44265,7 +45712,8 @@ function cells(flow) {
|
|
|
44265
45712
|
flow.binding ?? "",
|
|
44266
45713
|
flow.crosses.map(networkName3).join("; "),
|
|
44267
45714
|
flow.via ?? "",
|
|
44268
|
-
flow.evidence.join("; ")
|
|
45715
|
+
flow.evidence.join("; "),
|
|
45716
|
+
gateCell(flow)
|
|
44269
45717
|
];
|
|
44270
45718
|
}
|
|
44271
45719
|
function sortedKeys2(value) {
|
|
@@ -44282,16 +45730,19 @@ function markdownOf(flows3) {
|
|
|
44282
45730
|
const escape = (cell) => cell === "" ? " " : cell.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
44283
45731
|
const rows = flows3.map((f) => `| ${cells(f).map(escape).join(" | ")} |`);
|
|
44284
45732
|
const header = [`| ${COLUMNS.join(" | ")} |`, `|${COLUMNS.map(() => "---").join("|")}|`];
|
|
44285
|
-
|
|
45733
|
+
const refused3 = flows3.filter(refusedFlow);
|
|
45734
|
+
const warning = refused3.length > 0 ? [`> **The design fails the gate:** ${refused3.length} row(s) marked REFUSED are refused by \`wairon validate\`, not allowed. Fix the design before trusting this matrix.`, ...gateFindingsOf(flows3).map((l) => `> - ${l.replace(/\n/g, " ")}`), ""] : [];
|
|
45735
|
+
return [...warning, ...header, ...rows].join("\n") + "\n" + (flows3.length === 0 ? "\n_No network-transport flows: in-process and local verbs never produce one._\n" : "");
|
|
44286
45736
|
}
|
|
44287
45737
|
function encodeFlows(flows3, format) {
|
|
45738
|
+
const gate = { gateFindings: gateFindingsOf(flows3), refused: flows3.some(refusedFlow) };
|
|
44288
45739
|
switch (format) {
|
|
44289
45740
|
case "json":
|
|
44290
|
-
return { format, content: JSON.stringify(sortedKeys2(flows3), null, 2) + "\n", unbound: [] };
|
|
45741
|
+
return { format, content: JSON.stringify(sortedKeys2(flows3), null, 2) + "\n", unbound: [], ...gate };
|
|
44291
45742
|
case "csv":
|
|
44292
|
-
return { format, content: csvOf(flows3), unbound: [] };
|
|
45743
|
+
return { format, content: csvOf(flows3), unbound: [], ...gate };
|
|
44293
45744
|
default:
|
|
44294
|
-
return { format: "markdown", content: markdownOf(flows3), unbound: [] };
|
|
45745
|
+
return { format: "markdown", content: markdownOf(flows3), unbound: [], ...gate };
|
|
44295
45746
|
}
|
|
44296
45747
|
}
|
|
44297
45748
|
|
|
@@ -44304,7 +45755,7 @@ function flows(options, format) {
|
|
|
44304
45755
|
function why(options, from, to) {
|
|
44305
45756
|
const model = reachModel2(options);
|
|
44306
45757
|
const matrix = project2(model);
|
|
44307
|
-
return explain(matrix, from, to);
|
|
45758
|
+
return explain(matrix, from, to, model);
|
|
44308
45759
|
}
|
|
44309
45760
|
|
|
44310
45761
|
// src/mcp/network.ts
|
|
@@ -44320,6 +45771,10 @@ function flows2(to) {
|
|
|
44320
45771
|
function explain2(from, to) {
|
|
44321
45772
|
return why({}, from, to);
|
|
44322
45773
|
}
|
|
45774
|
+
function declare2(declared, description) {
|
|
45775
|
+
const network = declared ? description ? { description } : {} : null;
|
|
45776
|
+
return setNetwork(network);
|
|
45777
|
+
}
|
|
44323
45778
|
|
|
44324
45779
|
// src/migrations/orchestrator.ts
|
|
44325
45780
|
var path47 = __toESM(require("path"));
|
|
@@ -45629,7 +47084,7 @@ function place(path49, bytes) {
|
|
|
45629
47084
|
throw e;
|
|
45630
47085
|
}
|
|
45631
47086
|
}
|
|
45632
|
-
function
|
|
47087
|
+
function remove2(path49) {
|
|
45633
47088
|
retrying("remove", path49, () => {
|
|
45634
47089
|
try {
|
|
45635
47090
|
fs25.unlinkSync(path49);
|
|
@@ -46806,6 +48261,12 @@ function recordCrossing(plan6, bound2, member, result) {
|
|
|
46806
48261
|
for (const line of crossed.exported) plan6.edits.push({ project: bound2, kind: "export", detail: `export ${line}` });
|
|
46807
48262
|
for (const line of crossed.imported) plan6.edits.push({ project: bound2, kind: "external", detail: `import: ${line}` });
|
|
46808
48263
|
for (const line of crossed.consumersDropped) plan6.edits.push({ project: bound2, kind: "reference", detail: `consumers across the boundary to ${member} dropped: ${line}` });
|
|
48264
|
+
for (const line of crossed.entriesDeclared ?? []) plan6.edits.push({ project: member, kind: "reference", detail: `entry declared for the callers left across the boundary \u2014 ${line}` });
|
|
48265
|
+
const topics = crossed.topicsCrossing ?? [];
|
|
48266
|
+
if (topics.length > 0) {
|
|
48267
|
+
plan6.notes.push(`${topics.length} topic(s) now cross the boundary to "${member}"; the family run (\`wairon validate\` at ${label6(bound2)}) pairs them, while "${member}" validated alone reports them:`);
|
|
48268
|
+
for (const line of topics) plan6.notes.push(` ${line}`);
|
|
48269
|
+
}
|
|
46809
48270
|
}
|
|
46810
48271
|
function writeOne3(rehearsal, w) {
|
|
46811
48272
|
const args = w.args;
|
|
@@ -47238,7 +48699,7 @@ function swap(journals) {
|
|
|
47238
48699
|
function swapEntry(journal, entry) {
|
|
47239
48700
|
const target = liveOf(journal.owner, entry.path);
|
|
47240
48701
|
if (entry.action === "delete") {
|
|
47241
|
-
guarded(journal, `remove ${target}`, () =>
|
|
48702
|
+
guarded(journal, `remove ${target}`, () => remove2(target));
|
|
47242
48703
|
guarded(journal, `prune ${path46.dirname(target)}`, () => {
|
|
47243
48704
|
for (const d of prune2(path46.dirname(target), journal.owner)) if (!journal.dirsRemoved.includes(d)) journal.dirsRemoved.push(d);
|
|
47244
48705
|
});
|
|
@@ -47280,7 +48741,7 @@ function restoreOwner(journal) {
|
|
|
47280
48741
|
}
|
|
47281
48742
|
for (const entry of journal.entries.filter((e) => e.action === "create")) {
|
|
47282
48743
|
const target = liveOf(journal.owner, entry.path);
|
|
47283
|
-
if (digest2(target) === entry.stagedDigest) attempt(() =>
|
|
48744
|
+
if (digest2(target) === entry.stagedDigest) attempt(() => remove2(target));
|
|
47284
48745
|
}
|
|
47285
48746
|
for (const d of [...journal.dirsCreated].sort((a, b) => b.length - a.length)) attempt(() => prune2(d, path46.dirname(d)));
|
|
47286
48747
|
for (const entry of journal.entries) {
|
|
@@ -47580,8 +49041,8 @@ function relockOf(plan6, changes) {
|
|
|
47580
49041
|
const moved = movedOf(plan6);
|
|
47581
49042
|
const relocated = moved !== null && approvalRecord(moved.from) !== null ? [moved.to] : [];
|
|
47582
49043
|
if (plan6.request.verb !== "rename") return [...owners.filter((owner) => approvalRecord(owner) !== null), ...relocated];
|
|
47583
|
-
const
|
|
47584
|
-
const first =
|
|
49044
|
+
const renamed2 = plan6.edits.find((e) => e.write?.call === "renameId")?.write?.root;
|
|
49045
|
+
const first = renamed2 !== void 0 ? path47.resolve(renamed2) : void 0;
|
|
47585
49046
|
return [...owners.filter((o) => o === first), ...owners.filter((o) => o !== first)];
|
|
47586
49047
|
}
|
|
47587
49048
|
function apply(plan6) {
|
|
@@ -47801,11 +49262,27 @@ var externalAdditionOutput = {
|
|
|
47801
49262
|
unreachable: import_zod12.z.string().optional().describe("Set when the declaration was written but its producer could not be read: why, and that nothing was pinned."),
|
|
47802
49263
|
...staleServerOutput
|
|
47803
49264
|
};
|
|
49265
|
+
var externalRemovalOutput = {
|
|
49266
|
+
alias: import_zod12.z.string(),
|
|
49267
|
+
removed: import_zod12.z.boolean().describe("Whether .wai/project.yaml no longer carries the declaration because this call took it out."),
|
|
49268
|
+
unpinned: import_zod12.z.boolean().describe("Whether the alias's lock entry and pinned snapshot were removed (or would be, on a dry run)."),
|
|
49269
|
+
refusal: import_zod12.z.string().optional().describe("Why nothing was removed, in one sentence."),
|
|
49270
|
+
...staleServerOutput
|
|
49271
|
+
};
|
|
49272
|
+
var externalUseChangeOutput = {
|
|
49273
|
+
alias: import_zod12.z.string(),
|
|
49274
|
+
written: import_zod12.z.boolean().describe("Whether .wai/project.yaml now carries the changed `use` (false on a refusal, a dry run and when nothing changed)."),
|
|
49275
|
+
use: import_zod12.z.array(import_zod12.z.string()).describe("The `use` list after the change, in order."),
|
|
49276
|
+
added: import_zod12.z.array(import_zod12.z.string()).describe("The names this change imports that were not imported before."),
|
|
49277
|
+
removed: import_zod12.z.array(import_zod12.z.string()).describe("The names this change stops importing."),
|
|
49278
|
+
refusal: import_zod12.z.string().optional().describe("Why nothing was written, in one sentence."),
|
|
49279
|
+
...staleServerOutput
|
|
49280
|
+
};
|
|
47804
49281
|
function additionAnswer(request, addition) {
|
|
47805
49282
|
if (addition.refusal) {
|
|
47806
49283
|
return { ...structured(addition.refusal, { ...addition }), isError: true };
|
|
47807
49284
|
}
|
|
47808
|
-
const summary = request.dryRun ? `Dry run: would declare "${addition.alias}" \u2192 ${addition.project ?? addition.alias} as ${JSON.stringify(addition.declaration)}
|
|
49285
|
+
const summary = request.dryRun ? `Dry run: would declare "${addition.alias}" \u2192 ${addition.project ?? addition.alias} as ${JSON.stringify(addition.declaration)}${addition.unreachable ? `; ${addition.unreachable}` : "; the producer was read and agrees"}. Nothing was written.` : `Declared "${addition.alias}" \u2192 ${addition.project ?? addition.alias} as ${JSON.stringify(addition.declaration)}` + (addition.pin ? `; pin ${addition.pin.outcome}${addition.pin.detail ? ` \u2014 ${addition.pin.detail}` : ""}` : "") + (addition.unreachable ? `. Declared but not pinned: ${addition.unreachable}` : "") + ".";
|
|
47809
49286
|
return structured(summary, { ...addition });
|
|
47810
49287
|
}
|
|
47811
49288
|
var flowPartyOutput = import_zod12.z.object({
|
|
@@ -47824,7 +49301,9 @@ var networkFlowOutput = import_zod12.z.object({
|
|
|
47824
49301
|
crosses: import_zod12.z.array(import_zod12.z.string()),
|
|
47825
49302
|
via: import_zod12.z.string().optional(),
|
|
47826
49303
|
evidence: import_zod12.z.array(import_zod12.z.string()),
|
|
47827
|
-
verbs: import_zod12.z.array(import_zod12.z.string()).optional()
|
|
49304
|
+
verbs: import_zod12.z.array(import_zod12.z.string()).optional(),
|
|
49305
|
+
refusedBy: import_zod12.z.array(import_zod12.z.string()).optional().describe("The gate's error findings on this flow (`CODE: message`): the design refuses it, so it is never allowed."),
|
|
49306
|
+
flaggedBy: import_zod12.z.array(import_zod12.z.string()).optional().describe("The gate's warnings and notices on this flow (`CODE: message`): allowed, but marked.")
|
|
47828
49307
|
});
|
|
47829
49308
|
var networkFlowsOutput = {
|
|
47830
49309
|
flows: import_zod12.z.array(networkFlowOutput).describe("One allowed flow per row: who reaches which Portal verb, over which transport and binding, through which networks and gateway, on what evidence."),
|
|
@@ -47833,7 +49312,15 @@ var networkFlowsOutput = {
|
|
|
47833
49312
|
var flowExplanationOutput = {
|
|
47834
49313
|
allowed: import_zod12.z.boolean().describe("Whether any flow of the design allows it."),
|
|
47835
49314
|
flows: import_zod12.z.array(networkFlowOutput).describe("The flows that allow it."),
|
|
47836
|
-
chain: import_zod12.z.array(import_zod12.z.string()).describe("Each step of the justification, one line each; when not allowed, what reaches the callee instead."),
|
|
49315
|
+
chain: import_zod12.z.array(import_zod12.z.string()).describe("Each step of the justification, one line each; when not allowed, why not: refused by the gate, in-process, an unknown party, or what reaches the callee instead."),
|
|
49316
|
+
inProcess: import_zod12.z.boolean().optional().describe("True when the callee takes no network flow at all: it is reached in-process, never over the network."),
|
|
49317
|
+
unknown: import_zod12.z.array(import_zod12.z.string()).optional().describe("The party names the design does not know: a typo, not a refusal."),
|
|
49318
|
+
refusedBy: import_zod12.z.array(import_zod12.z.string()).optional().describe("The gate's error findings on the flows between the pair: the design names them, the gate refuses them."),
|
|
49319
|
+
...staleServerOutput
|
|
49320
|
+
};
|
|
49321
|
+
var networkDeclarationOutput = {
|
|
49322
|
+
written: import_zod12.z.boolean().describe("Whether .wai/project.yaml changed: false when it already read that way."),
|
|
49323
|
+
declared: import_zod12.z.boolean().describe("Whether the project now declares a network."),
|
|
47837
49324
|
...staleServerOutput
|
|
47838
49325
|
};
|
|
47839
49326
|
function flowLine(f) {
|
|
@@ -48338,6 +49825,7 @@ var SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
48338
49825
|
"sdd_rename_component",
|
|
48339
49826
|
"sdd_rename_method",
|
|
48340
49827
|
"sdd_rename_type",
|
|
49828
|
+
"sdd_rename_field",
|
|
48341
49829
|
"sdd_add_component",
|
|
48342
49830
|
"sdd_define_interface",
|
|
48343
49831
|
"sdd_set_endpoints",
|
|
@@ -48884,6 +50372,27 @@ ${renderChangeReport(report3)}`,
|
|
|
48884
50372
|
}
|
|
48885
50373
|
}
|
|
48886
50374
|
);
|
|
50375
|
+
reg(
|
|
50376
|
+
server,
|
|
50377
|
+
"sdd_rename_field",
|
|
50378
|
+
{
|
|
50379
|
+
description: "Rename a field of a type and respell every reference to it in the bound tree: a foreign key's `references: <type>.<field>` naming it from any type (the type's own self-references included). The field keeps its place, type and description under the new name, and its old name joins the field's rename trace (previousNames), which the design export shows as `formerly` so a consumer holding the old name can follow the move. Rename traces and prose are never rewritten. Refuses, writing nothing: a type that does not exist (type-missing), one in another project (chained-type), a field the type does not declare (field-missing), a new name that is not an identifier (invalid-name), and a name another field of the type holds (name-taken) or retired (name-retired \u2014 unsetting the holder's previousNames releases it). Returns the type, from and to, and the specs rewritten. The approval reads the type as changed until the next `wairon lock`, as with any edit.",
|
|
50380
|
+
inputSchema: {
|
|
50381
|
+
id: import_zod12.z.string().describe("The type whose field is renamed (qualified by its owning subsystem if needed, `subsystem::id`)"),
|
|
50382
|
+
field: import_zod12.z.string().describe("The field's name as it stands"),
|
|
50383
|
+
newName: import_zod12.z.string().describe("Its new name: an identifier")
|
|
50384
|
+
}
|
|
50385
|
+
},
|
|
50386
|
+
({ id, field, newName }) => {
|
|
50387
|
+
try {
|
|
50388
|
+
const bare = id.startsWith("::") ? id.slice(2) : id;
|
|
50389
|
+
const resolved = loadTypeSpec(bare) ? bare : id;
|
|
50390
|
+
return json(renameField(resolved, field, newName));
|
|
50391
|
+
} catch (e) {
|
|
50392
|
+
return errText(String(e));
|
|
50393
|
+
}
|
|
50394
|
+
}
|
|
50395
|
+
);
|
|
48887
50396
|
reg(
|
|
48888
50397
|
server,
|
|
48889
50398
|
"sdd_move_methods",
|
|
@@ -48916,7 +50425,7 @@ ${renderChangeReport(report3)}`,
|
|
|
48916
50425
|
componentType: import_zod12.z.enum(["Portal", "Orchestrator", "Supervisor", "Actor", "Store", "Index", "Query", "Registry", "Adapter", "Observer", "Repository"]).describe("The building block, or the pattern Repository. Specialist and Gateway are retired and cannot be authored: logic is an Orchestrator with a dependencyClass, a gateway is a Portal with the gateway variant"),
|
|
48917
50426
|
owns: import_zod12.z.array(import_zod12.z.string()).optional().describe("Member block ids privately owned by this component (patterns only)"),
|
|
48918
50427
|
dependsOn: import_zod12.z.array(import_zod12.z.string()).optional().describe("IDs of other components this collaborates with (facades or standalone blocks)"),
|
|
48919
|
-
transport: import_zod12.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "JSONRPC", "InProcess", "Custom"]).optional().describe("
|
|
50428
|
+
transport: import_zod12.z.enum(["HTTP", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "JSONRPC", "InProcess", "Custom"]).optional().describe("Expected on every Portal: how callers reach its verbs, one vocabulary with the endpoint discriminator. Network: HTTP, gRPC, GraphQL, MessageBus, Custom. Local: CLI, NamedPipe, IPC, JSONRPC (JSON-RPC over stdio, e.g. a language server or stdio MCP). InProcess: a library (or FFI/DLL with an abi), whose verbs are its contract methods and bind no endpoint. Replaces the retired portalType (HTTP_API is HTTP). On an Adapter, optional: the transport it calls its target Portal over \u2014 omit it and it is taken from the Portals its dependsOn names when they share one; a stated one that differs from the target's is ADAPTER_TRANSPORT_MISMATCH. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
|
|
48920
50429
|
abi: import_zod12.z.string().optional().describe(`Portal-only, InProcess only: how a foreign language links this library \u2014 "c" (a C-ABI shared library, DLL or FFI) or "wasm" (a WebAssembly component). Omit for a native API in the project's targetLanguage.`),
|
|
48921
50430
|
invokedBy: import_zod12.z.object({
|
|
48922
50431
|
kind: import_zod12.z.enum(["entry"]).describe("entry: callers outside the design reach every verb of this Portal over its transport"),
|
|
@@ -48959,6 +50468,13 @@ ${renderChangeReport(report3)}`,
|
|
|
48959
50468
|
if (invokedBy?.scope !== void 0 && transport !== void 0 && transportKind(transport) !== "network") {
|
|
48960
50469
|
return errText(`Component "${id}": an entry scope applies only to a network transport, and "${transport}" is ${transportKind(transport)} \u2014 drop the scope.`);
|
|
48961
50470
|
}
|
|
50471
|
+
let inferredTransport;
|
|
50472
|
+
if (componentType === "Adapter" && transport === void 0) {
|
|
50473
|
+
const portals = (dependsOn ?? []).map((dep) => loadComponentSpec(dep)).filter((c) => c?.componentType === "Portal" && c.transport !== void 0);
|
|
50474
|
+
const kinds = [...new Set(portals.map((p) => p.transport))];
|
|
50475
|
+
if (kinds.length === 1) inferredTransport = kinds[0];
|
|
50476
|
+
}
|
|
50477
|
+
const statedTransport = transport ?? inferredTransport;
|
|
48962
50478
|
const restatement = {
|
|
48963
50479
|
kind: "component",
|
|
48964
50480
|
spec: {
|
|
@@ -48969,7 +50485,7 @@ ${renderChangeReport(report3)}`,
|
|
|
48969
50485
|
componentType,
|
|
48970
50486
|
owns: owns ?? [],
|
|
48971
50487
|
dependsOn: dependsOn ?? [],
|
|
48972
|
-
...
|
|
50488
|
+
...statedTransport ? { transport: statedTransport } : {},
|
|
48973
50489
|
...abi ? { abi } : {},
|
|
48974
50490
|
...invokedBy ? { invokedBy } : {},
|
|
48975
50491
|
...basePath ? { basePath } : {},
|
|
@@ -48984,8 +50500,9 @@ ${renderChangeReport(report3)}`,
|
|
|
48984
50500
|
status
|
|
48985
50501
|
};
|
|
48986
50502
|
const receipt = writeSpec(restatement);
|
|
50503
|
+
const inferred = inferredTransport ? ` Its transport, ${inferredTransport}, was taken from the Portal it calls (an Adapter calls its target's verbs over the target's transport).` : "";
|
|
48987
50504
|
return writeReceipt(
|
|
48988
|
-
`Successfully ${receipt.replacedExisting ? "re-authored" : "added"} L2 Component Spec "${name}" (${id}, ${componentType})
|
|
50505
|
+
`Successfully ${receipt.replacedExisting ? "re-authored" : "added"} L2 Component Spec "${name}" (${id}, ${componentType}).${inferred}`,
|
|
48989
50506
|
receipt
|
|
48990
50507
|
);
|
|
48991
50508
|
} catch (e) {
|
|
@@ -49641,6 +51158,58 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
49641
51158
|
}
|
|
49642
51159
|
}
|
|
49643
51160
|
);
|
|
51161
|
+
reg(
|
|
51162
|
+
server,
|
|
51163
|
+
"sdd_remove_external",
|
|
51164
|
+
{
|
|
51165
|
+
description: "Remove one external of the bound project: its declaration in .wai/project.yaml and its pin (the lock entry and the pinned snapshot) together, so no orphaned pin is left; an orphaned pin alone (a declaration someone deleted by hand) is removed too. Refused in one sentence when the alias is neither declared nor pinned. References to the alias are the validator's to report afterwards. Use it instead of editing .wai/project.yaml by hand. A tree-scoped write.",
|
|
51166
|
+
inputSchema: {
|
|
51167
|
+
alias: import_zod12.z.string().describe("The external to remove"),
|
|
51168
|
+
dryRun: import_zod12.z.boolean().optional().describe("Answer what would be removed, writing nothing")
|
|
51169
|
+
},
|
|
51170
|
+
outputSchema: externalRemovalOutput
|
|
51171
|
+
},
|
|
51172
|
+
({ alias, dryRun }) => {
|
|
51173
|
+
try {
|
|
51174
|
+
const removal = removeExternal2(alias, dryRun);
|
|
51175
|
+
if (removal.refusal) return { ...structured(removal.refusal, { ...removal }), isError: true };
|
|
51176
|
+
const what = [removal.removed || dryRun ? "its declaration" : "", removal.unpinned ? "its pin (lock entry and snapshot)" : ""].filter(Boolean).join(" and ") || "nothing more";
|
|
51177
|
+
return structured(dryRun ? `Dry run: would remove "${alias}" \u2014 ${what}. Nothing was written.` : `Removed "${alias}": ${what}.`, { ...removal });
|
|
51178
|
+
} catch (e) {
|
|
51179
|
+
return errMessage(e);
|
|
51180
|
+
}
|
|
51181
|
+
}
|
|
51182
|
+
);
|
|
51183
|
+
reg(
|
|
51184
|
+
server,
|
|
51185
|
+
"sdd_update_external",
|
|
51186
|
+
{
|
|
51187
|
+
description: "Add and remove `use` imports of one declared external, so this project's specs may name the producer's public names bare \u2014 e.g. so an `implements` of another project's trait may spell the producer's types as the producer does. An added name is checked against what the producer exports to this project (refused naming the closest exported names, or the audience that keeps it out). The pin is untouched. Use it instead of editing .wai/project.yaml by hand. A tree-scoped write.",
|
|
51188
|
+
inputSchema: {
|
|
51189
|
+
alias: import_zod12.z.string().describe("The declared external whose `use` changes"),
|
|
51190
|
+
addUse: import_zod12.z.array(import_zod12.z.string()).optional().describe("Public names to import bare (`*` for all)"),
|
|
51191
|
+
removeUse: import_zod12.z.array(import_zod12.z.string()).optional().describe("Names to stop importing"),
|
|
51192
|
+
dryRun: import_zod12.z.boolean().optional().describe("Answer the resulting list, writing nothing")
|
|
51193
|
+
},
|
|
51194
|
+
outputSchema: externalUseChangeOutput
|
|
51195
|
+
},
|
|
51196
|
+
({ alias, addUse, removeUse, dryRun }) => {
|
|
51197
|
+
try {
|
|
51198
|
+
const change = updateExternalUse({
|
|
51199
|
+
alias,
|
|
51200
|
+
...addUse !== void 0 ? { add: addUse } : {},
|
|
51201
|
+
...removeUse !== void 0 ? { remove: removeUse } : {},
|
|
51202
|
+
...dryRun ? { dryRun: true } : {}
|
|
51203
|
+
});
|
|
51204
|
+
if (change.refusal) return { ...structured(change.refusal, { ...change }), isError: true };
|
|
51205
|
+
const moved = [change.added.length ? `+ ${change.added.join(", ")}` : "", change.removed.length ? `- ${change.removed.join(", ")}` : ""].filter(Boolean).join("; ") || "no change";
|
|
51206
|
+
const list = change.use.length ? change.use.join(", ") : "(none)";
|
|
51207
|
+
return structured(`${dryRun ? "Dry run: " : ""}${alias}.use ${dryRun ? "would be" : change.written ? "is now" : "is already"} [${list}] (${moved}).`, { ...change });
|
|
51208
|
+
} catch (e) {
|
|
51209
|
+
return errMessage(e);
|
|
51210
|
+
}
|
|
51211
|
+
}
|
|
51212
|
+
);
|
|
49644
51213
|
reg(
|
|
49645
51214
|
server,
|
|
49646
51215
|
"sdd_get_network_flows",
|
|
@@ -49675,13 +51244,35 @@ ${testsBlock}` : ""}`, deletion);
|
|
|
49675
51244
|
({ from, to }) => {
|
|
49676
51245
|
try {
|
|
49677
51246
|
const explanation = explain2(from, to);
|
|
49678
|
-
const head2 = explanation.allowed ? `${from} may reach ${to}:` : `${from} may NOT reach ${to}:`;
|
|
51247
|
+
const head2 = explanation.allowed ? `${from} may reach ${to}:` : explanation.unknown?.length ? `unknown party: ${explanation.unknown.join(", ")}:` : explanation.refusedBy?.length ? `${from} may NOT reach ${to} \u2014 the gate refuses the flow:` : explanation.inProcess ? `${to} is reached in-process \u2014 never a network flow:` : `${from} may NOT reach ${to}:`;
|
|
49679
51248
|
return structured([head2, ...explanation.chain.map((l) => ` ${l}`)].join("\n"), { ...explanation });
|
|
49680
51249
|
} catch (e) {
|
|
49681
51250
|
return errMessage(e);
|
|
49682
51251
|
}
|
|
49683
51252
|
}
|
|
49684
51253
|
);
|
|
51254
|
+
reg(
|
|
51255
|
+
server,
|
|
51256
|
+
"sdd_set_network",
|
|
51257
|
+
{
|
|
51258
|
+
description: "Declare (declared: true, with an optional description) or remove (declared: false) the bound project's `network` in .wai/project.yaml: that this project and every member below it form one isolated network boundary. It changes which rules fire (GATEWAY_BYPASSED, ENTRY_UNPROVEN, ...), so run sdd_validate_tree after it; it is part of the gate identity, so the human re-locks. Use it instead of editing .wai/project.yaml by hand. A project-scoped write.",
|
|
51259
|
+
inputSchema: {
|
|
51260
|
+
declared: import_zod12.z.boolean().describe("true declares the network, false removes the declaration"),
|
|
51261
|
+
description: import_zod12.z.string().optional().describe("What the network encloses, in the project's words (shown on the network diagram); only with declared: true")
|
|
51262
|
+
},
|
|
51263
|
+
outputSchema: networkDeclarationOutput
|
|
51264
|
+
},
|
|
51265
|
+
({ declared, description }) => {
|
|
51266
|
+
try {
|
|
51267
|
+
if (!declared && description !== void 0) return errText("sdd_set_network: a description goes with declared: true \u2014 removing the declaration takes none.");
|
|
51268
|
+
const written = declare2(declared, description ?? null);
|
|
51269
|
+
const text3 = declared ? written ? `Declared this project's network${description ? ` ("${description}")` : ""}. Run sdd_validate_tree: the network rules now judge the design. The human re-locks once it validates.` : "This project already declares that network: nothing was written." : written ? "Removed this project's network declaration. Run sdd_validate_tree; the human re-locks." : "This project declares no network: nothing was removed.";
|
|
51270
|
+
return structured(text3, { written, declared });
|
|
51271
|
+
} catch (e) {
|
|
51272
|
+
return errMessage(e);
|
|
51273
|
+
}
|
|
51274
|
+
}
|
|
51275
|
+
);
|
|
49685
51276
|
reg(
|
|
49686
51277
|
server,
|
|
49687
51278
|
"sdd_get_agent_brief",
|
|
@@ -49883,6 +51474,7 @@ function loadProjectConfig3() {
|
|
|
49883
51474
|
SurfaceSnapshotSchema,
|
|
49884
51475
|
SurfaceTypeDefSchema,
|
|
49885
51476
|
SurfaceTypeExportSchema,
|
|
51477
|
+
SurfaceUnresolvedExportSchema,
|
|
49886
51478
|
SwitchCaseSchema,
|
|
49887
51479
|
SystemPublicInterfaceSchema,
|
|
49888
51480
|
SystemSpecSchema,
|
|
@@ -49916,12 +51508,14 @@ function loadProjectConfig3() {
|
|
|
49916
51508
|
approvalKeyIn,
|
|
49917
51509
|
approvalRecord,
|
|
49918
51510
|
approvalVerdict,
|
|
51511
|
+
approvedStatuses,
|
|
49919
51512
|
assertContainedProjectPath,
|
|
49920
51513
|
assertProjectInitialized,
|
|
49921
51514
|
attachRetiredMounts,
|
|
49922
51515
|
backendCommand,
|
|
49923
51516
|
backfillChainedSubprojectConfigs,
|
|
49924
51517
|
backupBeside,
|
|
51518
|
+
bindDeclaredExternal,
|
|
49925
51519
|
bodySitesOf,
|
|
49926
51520
|
buildCanvasDataModel,
|
|
49927
51521
|
buildProjectGraph,
|
|
@@ -49933,6 +51527,7 @@ function loadProjectConfig3() {
|
|
|
49933
51527
|
canonicalTypeRef,
|
|
49934
51528
|
canonicalTypeText,
|
|
49935
51529
|
captureApprovedSpecs,
|
|
51530
|
+
carriedFactChanges,
|
|
49936
51531
|
carryRetiredReachForms,
|
|
49937
51532
|
checkDeclarativePack,
|
|
49938
51533
|
clearLoaderIssues,
|
|
@@ -49944,6 +51539,7 @@ function loadProjectConfig3() {
|
|
|
49944
51539
|
composeRuleSequence,
|
|
49945
51540
|
computeGateStateId,
|
|
49946
51541
|
computeOwnDesignId,
|
|
51542
|
+
computeOwnDesignIdAsRecorded,
|
|
49947
51543
|
computeOwnStateId,
|
|
49948
51544
|
computeStateId,
|
|
49949
51545
|
computeStateIdAt,
|
|
@@ -50047,6 +51643,7 @@ function loadProjectConfig3() {
|
|
|
50047
51643
|
injectGuide,
|
|
50048
51644
|
installPackFromDirectory,
|
|
50049
51645
|
interfaceCanonicalTypes,
|
|
51646
|
+
interfaceDesignView,
|
|
50050
51647
|
interfaceGenericParameters,
|
|
50051
51648
|
internalizeMember,
|
|
50052
51649
|
isDraftSubsystem,
|
|
@@ -50066,6 +51663,7 @@ function loadProjectConfig3() {
|
|
|
50066
51663
|
languageOfSourcePath,
|
|
50067
51664
|
lineEndingFor,
|
|
50068
51665
|
listDirectChainedSubprojects,
|
|
51666
|
+
listExternalConsumers,
|
|
50069
51667
|
listExternals,
|
|
50070
51668
|
listFiles,
|
|
50071
51669
|
listFilesRecursive,
|
|
@@ -50167,6 +51765,7 @@ function loadProjectConfig3() {
|
|
|
50167
51765
|
removeSnapshot,
|
|
50168
51766
|
renameAlias,
|
|
50169
51767
|
renameComponent,
|
|
51768
|
+
renameField,
|
|
50170
51769
|
renameId,
|
|
50171
51770
|
renameMethod,
|
|
50172
51771
|
renameType,
|
|
@@ -50181,6 +51780,7 @@ function loadProjectConfig3() {
|
|
|
50181
51780
|
resolveChainingParent,
|
|
50182
51781
|
resolveDomains,
|
|
50183
51782
|
resolveExpectedOutputPaths,
|
|
51783
|
+
resolveExternalCandidate,
|
|
50184
51784
|
resolveExternals,
|
|
50185
51785
|
resolveImport,
|
|
50186
51786
|
resolveInstalledPack,
|
|
@@ -50209,6 +51809,7 @@ function loadProjectConfig3() {
|
|
|
50209
51809
|
setExecutionTier,
|
|
50210
51810
|
setId,
|
|
50211
51811
|
setLogLevel,
|
|
51812
|
+
setNetwork,
|
|
50212
51813
|
setProjectRoot,
|
|
50213
51814
|
setProjectType,
|
|
50214
51815
|
settledSpecPaths,
|
|
@@ -50222,6 +51823,7 @@ function loadProjectConfig3() {
|
|
|
50222
51823
|
stepGraph,
|
|
50223
51824
|
storedMethodSignature,
|
|
50224
51825
|
storedTypeMethod,
|
|
51826
|
+
subsystemDesignView,
|
|
50225
51827
|
syncContextFiles,
|
|
50226
51828
|
technologyName,
|
|
50227
51829
|
technologyTokens,
|
|
@@ -50235,6 +51837,7 @@ function loadProjectConfig3() {
|
|
|
50235
51837
|
typeDialectFor,
|
|
50236
51838
|
typeGenericParameters,
|
|
50237
51839
|
typeIsMany,
|
|
51840
|
+
typeMappingFor,
|
|
50238
51841
|
typeMatchesRef,
|
|
50239
51842
|
typeNamedRefs,
|
|
50240
51843
|
typeProblemEnumProposal,
|