@wairon/cli 5.1.1-dev.15 → 5.1.1-dev.17
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 +411 -318
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +397 -256
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -1
- package/dist/templates/skills/sdd-implement.md +1 -1
- package/dist/templates/skills/sdd-narrative.md +3 -1
- package/dist/templates/variants/logic-shapes.yaml +59 -0
- package/dist/templates/variants/portal-shapes.yaml +13 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.1.1-dev.
|
|
68
|
+
WAIRON_VERSION = "5.1.1-dev.17";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
SUPPORTED_ALIASES = ["wai"];
|
|
71
71
|
SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -382,10 +382,10 @@ ${GUIDE_MARKER_END}
|
|
|
382
382
|
fs4.writeFileSync(filePath, newContent, "utf-8");
|
|
383
383
|
}
|
|
384
384
|
function stripGuideSection(content) {
|
|
385
|
-
const
|
|
385
|
+
const start2 = content.indexOf(GUIDE_MARKER_START);
|
|
386
386
|
const end = content.indexOf(GUIDE_MARKER_END);
|
|
387
|
-
if (
|
|
388
|
-
return content.slice(0,
|
|
387
|
+
if (start2 === -1 || end === -1) return content;
|
|
388
|
+
return content.slice(0, start2) + content.slice(end + GUIDE_MARKER_END.length);
|
|
389
389
|
}
|
|
390
390
|
function writeRootGuideDelegator(projectRoot2, targetType) {
|
|
391
391
|
if (targetType === "claude") {
|
|
@@ -500,7 +500,7 @@ This project uses **wairon**. System specs live under \`.wai/specs/\` (L0 System
|
|
|
500
500
|
1. **Design before code**: Complete spec and pass validator before writing source code.
|
|
501
501
|
2. **Human-in-the-loop**: Ask user approval for each spec layer before proceeding.
|
|
502
502
|
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.
|
|
503
|
-
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
|
|
503
|
+
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 or Specialist. Two sanctioned shapes: the RECOMMENDED Repository pattern (owns Store + Registry + Index; consumers depend on the facade), or \u2014 for genuinely simple state \u2014 a deliberately standalone Store (workflow-layer consumers only, acknowledged with a lint.allow reason on the UNOWNED_STORE warning). Never combine Store/Registry/Index roles into one component, and never fold state into a consuming component because a link was refused.
|
|
504
504
|
|
|
505
505
|
### Component Vocabulary
|
|
506
506
|
* **Blocks**: Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist.
|
|
@@ -1118,7 +1118,7 @@ function defaultNarrativeDetail(component) {
|
|
|
1118
1118
|
if (componentType === "Portal" || componentType === "Observer" || componentType === "Adapter") {
|
|
1119
1119
|
return "calls-only";
|
|
1120
1120
|
}
|
|
1121
|
-
if (componentType === "Store" || componentType === "Index" || componentType === "Registry") {
|
|
1121
|
+
if (componentType === "Store" || componentType === "Index" || componentType === "Query" || componentType === "Registry") {
|
|
1122
1122
|
return "intent";
|
|
1123
1123
|
}
|
|
1124
1124
|
return "full";
|
|
@@ -1151,7 +1151,7 @@ function implementationSourceFiles(impl) {
|
|
|
1151
1151
|
for (const method2 of impl.methods ?? []) add(method2.sourcePath);
|
|
1152
1152
|
return files;
|
|
1153
1153
|
}
|
|
1154
|
-
var import_zod7, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
1154
|
+
var import_zod7, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, DependencyClassSchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, FindingDeclarationSchema, MethodSignatureSchema, INTENT_FLOOR_MIN_CHARS, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
1155
1155
|
var init_specs = __esm({
|
|
1156
1156
|
"src/models/specs.ts"() {
|
|
1157
1157
|
"use strict";
|
|
@@ -1317,21 +1317,27 @@ var init_specs = __esm({
|
|
|
1317
1317
|
"Actor",
|
|
1318
1318
|
"Store",
|
|
1319
1319
|
"Index",
|
|
1320
|
+
"Query",
|
|
1321
|
+
// Repository member for computed reads over its Store
|
|
1320
1322
|
"Registry",
|
|
1321
1323
|
"Adapter",
|
|
1322
1324
|
"Observer",
|
|
1323
|
-
"Specialist",
|
|
1324
1325
|
"View",
|
|
1325
1326
|
// Pure presenter/UI component
|
|
1326
1327
|
// Patterns (compositions of blocks)
|
|
1327
1328
|
"Repository",
|
|
1328
|
-
"Gateway",
|
|
1329
1329
|
"FeatureComponent",
|
|
1330
1330
|
// Logic Hook + View Presenter pattern
|
|
1331
|
-
"RouterComponent"
|
|
1331
|
+
"RouterComponent",
|
|
1332
1332
|
// Switch/routing component pattern
|
|
1333
|
+
// Retired stereotypes: still parsed so a tree using them loads and the
|
|
1334
|
+
// rules can report them (STEREOTYPE_RETIRED), never authored new
|
|
1335
|
+
"Specialist",
|
|
1336
|
+
// logic is an Orchestrator with a dependencyClass
|
|
1337
|
+
"Gateway"
|
|
1338
|
+
// a gateway is a Portal with the gateway variant
|
|
1333
1339
|
]);
|
|
1334
|
-
PATTERN_TYPES = /* @__PURE__ */ new Set(["Repository", "
|
|
1340
|
+
PATTERN_TYPES = /* @__PURE__ */ new Set(["Repository", "FeatureComponent", "RouterComponent"]);
|
|
1335
1341
|
PortalTypeSchema = import_zod7.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]);
|
|
1336
1342
|
DispatchBindingSchema = import_zod7.z.object({
|
|
1337
1343
|
/** Capability name exactly as dispatched at runtime (e.g. "shadow_module.get"). */
|
|
@@ -1344,6 +1350,7 @@ var init_specs = __esm({
|
|
|
1344
1350
|
description: import_zod7.z.string().optional()
|
|
1345
1351
|
});
|
|
1346
1352
|
DurabilitySchema = import_zod7.z.enum(["ram-projection", "durable", "read-through", "cache"]);
|
|
1353
|
+
DependencyClassSchema = import_zod7.z.enum(["pure", "read"]);
|
|
1347
1354
|
PatternRefSchema = import_zod7.z.object({
|
|
1348
1355
|
id: import_zod7.z.string(),
|
|
1349
1356
|
version: import_zod7.z.string().optional()
|
|
@@ -1398,6 +1405,8 @@ var init_specs = __esm({
|
|
|
1398
1405
|
dispatch: import_zod7.z.array(DispatchBindingSchema).optional(),
|
|
1399
1406
|
/** Store-only: whether held state survives restart (see DurabilitySchema). */
|
|
1400
1407
|
durability: DurabilitySchema.optional(),
|
|
1408
|
+
/** Orchestrator-only: what the logic may depend on; unset means a workflow (see DependencyClassSchema). */
|
|
1409
|
+
dependencyClass: DependencyClassSchema.optional(),
|
|
1401
1410
|
/** Topics this component publishes to (see EventBindingSchema). */
|
|
1402
1411
|
emits: import_zod7.z.array(EventBindingSchema).optional(),
|
|
1403
1412
|
/** Topics this component consumes (see EventBindingSchema) — typical on Observers. */
|
|
@@ -1488,9 +1497,10 @@ var init_specs = __esm({
|
|
|
1488
1497
|
}).optional(),
|
|
1489
1498
|
/**
|
|
1490
1499
|
* The finding codes this method can report, each with its default severity
|
|
1491
|
-
* and summary (see FindingDeclarationSchema). Each declared code must
|
|
1492
|
-
*
|
|
1493
|
-
* code is declared once per
|
|
1500
|
+
* and summary (see FindingDeclarationSchema). Each declared code must be
|
|
1501
|
+
* anchored in the method's source file as a string literal or a
|
|
1502
|
+
* property-access name (UNREALIZED_FINDING). A code is declared once per
|
|
1503
|
+
* method.
|
|
1494
1504
|
*/
|
|
1495
1505
|
findings: import_zod7.z.array(FindingDeclarationSchema).superRefine((findings, ctx) => {
|
|
1496
1506
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2822,15 +2832,13 @@ var init_hierarchy_integrity = __esm({
|
|
|
2822
2832
|
}
|
|
2823
2833
|
}
|
|
2824
2834
|
for (const impl of ctx.implementations) {
|
|
2825
|
-
const contract = ctx.interfaces.find((i) => i.id === impl.contract);
|
|
2826
|
-
const isDraftCtx = impl.status === "draft" || impl.status === "design" || contract && (ctx.isComponentDraft(contract.component) || contract.status === "draft" || contract.status === "design");
|
|
2827
2835
|
if (!ctx.interfaceIds.has(impl.contract)) {
|
|
2828
2836
|
ctx.addIssue(
|
|
2829
2837
|
"error",
|
|
2830
2838
|
"INVALID_INTERFACE_REFERENCE",
|
|
2831
2839
|
`Implementation "${impl.id}" references non-existent interface contract "${impl.contract}".`,
|
|
2832
2840
|
impl.id,
|
|
2833
|
-
|
|
2841
|
+
ctx.isImplementationDraft(impl)
|
|
2834
2842
|
);
|
|
2835
2843
|
}
|
|
2836
2844
|
}
|
|
@@ -2946,11 +2954,7 @@ var init_type_references2 = __esm({
|
|
|
2946
2954
|
for (const field of t.fields) {
|
|
2947
2955
|
const refs = fieldTypeRefs(t, field.type);
|
|
2948
2956
|
for (const ref of refs) {
|
|
2949
|
-
if (ctx.
|
|
2950
|
-
continue;
|
|
2951
|
-
}
|
|
2952
|
-
const resolved = ctx.types.find((spec) => typeMatchesRef(spec, ref));
|
|
2953
|
-
if (!resolved) {
|
|
2957
|
+
if (!ctx.isTypeResolved(ref, /* @__PURE__ */ new Set())) {
|
|
2954
2958
|
ctx.addIssue(
|
|
2955
2959
|
"error",
|
|
2956
2960
|
"UNDEFINED_TYPE_REFERENCE",
|
|
@@ -3182,14 +3186,27 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3182
3186
|
);
|
|
3183
3187
|
continue;
|
|
3184
3188
|
}
|
|
3185
|
-
const
|
|
3186
|
-
const
|
|
3187
|
-
const
|
|
3189
|
+
const target = step.targetComponent;
|
|
3190
|
+
const isCrossTreeForm = ctx.isExternalNamespaceRef(target);
|
|
3191
|
+
const caller = ctx.componentMap.get(contract.component);
|
|
3192
|
+
const fromSubsystem = caller?.subsystem;
|
|
3193
|
+
const isCollapsedForm = !isCrossTreeForm && fromSubsystem !== void 0 && ctx.isCollapsedCrossTreeRef(target, fromSubsystem);
|
|
3194
|
+
const reportUndeclared = (reaches, surfaceResolved) => {
|
|
3195
|
+
if (!caller || target === caller.id || caller.dependsOn.includes(target) || caller.owns.includes(target)) return;
|
|
3196
|
+
ctx.addIssue(
|
|
3197
|
+
"error",
|
|
3198
|
+
"UNDECLARED_DEPENDENCY_CALL",
|
|
3199
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${caller.id}") ${reaches} component "${target}" (step ${step.stepNumber}) but component "${caller.id}" does not list "${target}" as a dependency.`,
|
|
3200
|
+
impl.id,
|
|
3201
|
+
isDraftCtx || ctx.isComponentDraft(caller.id),
|
|
3202
|
+
surfaceResolved
|
|
3203
|
+
);
|
|
3204
|
+
};
|
|
3188
3205
|
if (step.type === "dispatch") {
|
|
3189
|
-
const dispatchTarget = ctx.componentMap.get(
|
|
3206
|
+
const dispatchTarget = ctx.componentMap.get(target);
|
|
3190
3207
|
if (!dispatchTarget) {
|
|
3191
3208
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3192
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3209
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3193
3210
|
if (resolved.kind === "ambiguous") {
|
|
3194
3211
|
ctx.addIssue(
|
|
3195
3212
|
"error",
|
|
@@ -3197,7 +3214,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3197
3214
|
ambiguityMessage(
|
|
3198
3215
|
resolved,
|
|
3199
3216
|
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches (step ${step.stepNumber}) through`,
|
|
3200
|
-
|
|
3217
|
+
target
|
|
3201
3218
|
),
|
|
3202
3219
|
impl.id,
|
|
3203
3220
|
isDraftCtx
|
|
@@ -3205,11 +3222,12 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3205
3222
|
continue;
|
|
3206
3223
|
}
|
|
3207
3224
|
if (resolved.kind === "resolved") {
|
|
3225
|
+
reportUndeclared("dispatches through", true);
|
|
3208
3226
|
if (step.capability && !(resolved.entry.dispatch ?? []).some((b) => b.capability === step.capability)) {
|
|
3209
3227
|
ctx.addIssue(
|
|
3210
3228
|
"error",
|
|
3211
3229
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3212
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${
|
|
3230
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches capability "${step.capability}" through cross-tree portal "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not serve that capability on "${resolved.entry.id}".`,
|
|
3213
3231
|
impl.id,
|
|
3214
3232
|
isDraftCtx,
|
|
3215
3233
|
true
|
|
@@ -3222,7 +3240,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3222
3240
|
ctx.addIssue(
|
|
3223
3241
|
"warning",
|
|
3224
3242
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3225
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${
|
|
3243
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through cross-tree component "${target}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
3226
3244
|
impl.id,
|
|
3227
3245
|
isDraftCtx
|
|
3228
3246
|
);
|
|
@@ -3230,23 +3248,14 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3230
3248
|
ctx.addIssue(
|
|
3231
3249
|
"error",
|
|
3232
3250
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3233
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${
|
|
3251
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" dispatches through component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3234
3252
|
impl.id,
|
|
3235
3253
|
isDraftCtx
|
|
3236
3254
|
);
|
|
3237
3255
|
}
|
|
3238
3256
|
continue;
|
|
3239
3257
|
}
|
|
3240
|
-
|
|
3241
|
-
if (dispatchCaller && step.targetComponent !== dispatchCaller.id && !dispatchCaller.dependsOn.includes(step.targetComponent) && !dispatchCaller.owns.includes(step.targetComponent)) {
|
|
3242
|
-
ctx.addIssue(
|
|
3243
|
-
"error",
|
|
3244
|
-
"UNDECLARED_DEPENDENCY_CALL",
|
|
3245
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${dispatchCaller.id}") dispatches through component "${step.targetComponent}" (step ${step.stepNumber}) but component "${dispatchCaller.id}" does not list "${step.targetComponent}" as a dependency.`,
|
|
3246
|
-
impl.id,
|
|
3247
|
-
isDraftCtx || ctx.isComponentDraft(dispatchCaller.id)
|
|
3248
|
-
);
|
|
3249
|
-
}
|
|
3258
|
+
reportUndeclared("dispatches through");
|
|
3250
3259
|
continue;
|
|
3251
3260
|
}
|
|
3252
3261
|
const verb = step.type === "register" ? "registers callback" : "calls";
|
|
@@ -3260,10 +3269,10 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3260
3269
|
);
|
|
3261
3270
|
continue;
|
|
3262
3271
|
}
|
|
3263
|
-
const targetComp = ctx.componentMap.get(
|
|
3272
|
+
const targetComp = ctx.componentMap.get(target);
|
|
3264
3273
|
if (!targetComp) {
|
|
3265
3274
|
if (isCrossTreeForm || isCollapsedForm) {
|
|
3266
|
-
const resolved = ctx.resolveSurfaceRef(
|
|
3275
|
+
const resolved = ctx.resolveSurfaceRef(target, fromSubsystem);
|
|
3267
3276
|
if (resolved.kind === "ambiguous") {
|
|
3268
3277
|
ctx.addIssue(
|
|
3269
3278
|
"error",
|
|
@@ -3271,7 +3280,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3271
3280
|
ambiguityMessage(
|
|
3272
3281
|
resolved,
|
|
3273
3282
|
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" (step ${step.stepNumber}) on`,
|
|
3274
|
-
|
|
3283
|
+
target
|
|
3275
3284
|
),
|
|
3276
3285
|
impl.id,
|
|
3277
3286
|
isDraftCtx
|
|
@@ -3279,12 +3288,13 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3279
3288
|
continue;
|
|
3280
3289
|
}
|
|
3281
3290
|
if (resolved.kind === "resolved") {
|
|
3291
|
+
reportUndeclared(verb, true);
|
|
3282
3292
|
const surfaceMethod = resolved.entry.methods.find((m) => m.name === step.targetMethod);
|
|
3283
3293
|
if (!surfaceMethod) {
|
|
3284
3294
|
ctx.addIssue(
|
|
3285
3295
|
"error",
|
|
3286
3296
|
"SURFACE_REF_NOT_EXPOSED",
|
|
3287
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${
|
|
3297
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} "${step.targetMethod}" on cross-tree component "${target}" (step ${step.stepNumber}), but the surface snapshot of "${resolved.snapshot.projectName}" does not expose that method on "${resolved.entry.id}".`,
|
|
3288
3298
|
impl.id,
|
|
3289
3299
|
isDraftCtx,
|
|
3290
3300
|
true
|
|
@@ -3311,7 +3321,7 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3311
3321
|
ctx.addIssue(
|
|
3312
3322
|
"warning",
|
|
3313
3323
|
"CROSS_TREE_REF_UNRESOLVED",
|
|
3314
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${
|
|
3324
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} cross-tree component "${target}" (step ${step.stepNumber}), and no surface snapshot covers it \u2014 validate from the parent project, pin the family surfaces ("wairon surface pin"), or import the producing project's surface.`,
|
|
3315
3325
|
impl.id,
|
|
3316
3326
|
isDraftCtx
|
|
3317
3327
|
);
|
|
@@ -3319,26 +3329,15 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3319
3329
|
ctx.addIssue(
|
|
3320
3330
|
"error",
|
|
3321
3331
|
"INVALID_TARGET_COMPONENT_REFERENCE",
|
|
3322
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${
|
|
3332
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} component "${target}" which does not exist (step ${step.stepNumber}).`,
|
|
3323
3333
|
impl.id,
|
|
3324
3334
|
isDraftCtx
|
|
3325
3335
|
);
|
|
3326
3336
|
}
|
|
3327
3337
|
continue;
|
|
3328
3338
|
}
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
if (!callingComponent.dependsOn.includes(step.targetComponent) && !callingComponent.owns.includes(step.targetComponent)) {
|
|
3332
|
-
ctx.addIssue(
|
|
3333
|
-
"error",
|
|
3334
|
-
"UNDECLARED_DEPENDENCY_CALL",
|
|
3335
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" (component "${callingComponent.id}") ${verb} component "${step.targetComponent}" (step ${step.stepNumber}) but component "${callingComponent.id}" does not list "${step.targetComponent}" as a dependency.`,
|
|
3336
|
-
impl.id,
|
|
3337
|
-
isDraftCtx || ctx.isComponentDraft(callingComponent.id)
|
|
3338
|
-
);
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
const targetInterfaces = ctx.interfacesByComponent.get(step.targetComponent) ?? [];
|
|
3339
|
+
reportUndeclared(verb);
|
|
3340
|
+
const targetInterfaces = ctx.interfacesByComponent.get(target) ?? [];
|
|
3342
3341
|
let targetMethodSpec;
|
|
3343
3342
|
for (const targetIntf of targetInterfaces) {
|
|
3344
3343
|
const found = targetIntf.methods.find((m) => m.name === step.targetMethod);
|
|
@@ -3351,9 +3350,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3351
3350
|
ctx.addIssue(
|
|
3352
3351
|
"error",
|
|
3353
3352
|
"INVALID_TARGET_METHOD_REFERENCE",
|
|
3354
|
-
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${
|
|
3353
|
+
`Method "${implMethod.name}" in implementation "${impl.id}" ${verb} method "${step.targetMethod}" on component "${target}" which is not defined on any of its interfaces (step ${step.stepNumber}).`,
|
|
3355
3354
|
impl.id,
|
|
3356
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3355
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3357
3356
|
);
|
|
3358
3357
|
} else {
|
|
3359
3358
|
const declared = new Set(targetMethodSpec.guarantees ?? []);
|
|
@@ -3363,9 +3362,9 @@ var init_contract_symmetry_and_narratives = __esm({
|
|
|
3363
3362
|
ctx.addIssue(
|
|
3364
3363
|
"warning",
|
|
3365
3364
|
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
3366
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${
|
|
3365
|
+
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" explicitly asserts guarantee "${g}", but the method it calls \u2014 "${step.targetMethod}" on "${target}" \u2014 does not list "${g}" among its L3 contract guarantees. Declare it on that method (and ensure its shape can deliver it), or revise the narrative.`,
|
|
3367
3366
|
impl.id,
|
|
3368
|
-
isDraftCtx || ctx.isComponentDraft(
|
|
3367
|
+
isDraftCtx || ctx.isComponentDraft(target)
|
|
3369
3368
|
);
|
|
3370
3369
|
}
|
|
3371
3370
|
}
|
|
@@ -3695,43 +3694,47 @@ var init_narrative_flow = __esm({
|
|
|
3695
3694
|
});
|
|
3696
3695
|
|
|
3697
3696
|
// src/core/rules/narrative/narrative-antipatterns.ts
|
|
3698
|
-
function
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
}
|
|
3697
|
+
function completedStepGraph(steps) {
|
|
3698
|
+
const numbers = steps.flatMap((s) => [
|
|
3699
|
+
s.stepNumber,
|
|
3700
|
+
s.onTrueStep,
|
|
3701
|
+
s.onFalseStep,
|
|
3702
|
+
s.defaultStep,
|
|
3703
|
+
s.endStep,
|
|
3704
|
+
s.finallyStep,
|
|
3705
|
+
s.toStep,
|
|
3706
|
+
...(s.cases ?? []).map((c) => c.step),
|
|
3707
|
+
...(s.catches ?? []).map((c) => c.step),
|
|
3708
|
+
...(s.branches ?? []).map((b) => b.step)
|
|
3709
|
+
]).filter((n) => n !== void 0);
|
|
3710
|
+
const completion = {
|
|
3711
|
+
stepNumber: Math.max(0, ...numbers) + 1,
|
|
3712
|
+
description: "The method completes by falling off the end of its narrative",
|
|
3713
|
+
type: "return"
|
|
3714
|
+
};
|
|
3715
|
+
return stepGraph({ narrative: [...steps, completion] });
|
|
3717
3716
|
}
|
|
3718
3717
|
function isUnavoidable(steps, target) {
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
const
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
const
|
|
3726
|
-
if (
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3718
|
+
if (steps.length === 0) return false;
|
|
3719
|
+
const graph = completedStepGraph(steps);
|
|
3720
|
+
const completes = /* @__PURE__ */ new Set();
|
|
3721
|
+
const canComplete = (n) => {
|
|
3722
|
+
const s = graph.stepAt(n);
|
|
3723
|
+
if (s.type === "return" || s.type === "throw") return true;
|
|
3724
|
+
const arms = s.type === "parallel" ? (s.branches ?? []).map((b) => b.step).filter((e) => graph.stepAt(e) !== void 0) : [];
|
|
3725
|
+
if (arms.length) return arms.every((e) => completes.has(e));
|
|
3726
|
+
return graph.successorsOf(n).some((t) => completes.has(t));
|
|
3727
|
+
};
|
|
3728
|
+
const order = [...graph.stepNumbers].reverse();
|
|
3729
|
+
for (let grew = true; grew; ) {
|
|
3730
|
+
grew = false;
|
|
3731
|
+
for (const n of order) {
|
|
3732
|
+
if (n === target || completes.has(n) || !canComplete(n)) continue;
|
|
3733
|
+
completes.add(n);
|
|
3734
|
+
grew = true;
|
|
3732
3735
|
}
|
|
3733
3736
|
}
|
|
3734
|
-
return
|
|
3737
|
+
return !completes.has(graph.stepNumbers[0]);
|
|
3735
3738
|
}
|
|
3736
3739
|
function stronglyConnected(nums, successorsOf) {
|
|
3737
3740
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -3783,11 +3786,12 @@ function stronglyConnected(nums, successorsOf) {
|
|
|
3783
3786
|
}
|
|
3784
3787
|
return sccs;
|
|
3785
3788
|
}
|
|
3786
|
-
var narrativeAntipatternsRule;
|
|
3789
|
+
var byCodeUnit, narrativeAntipatternsRule;
|
|
3787
3790
|
var init_narrative_antipatterns = __esm({
|
|
3788
3791
|
"src/core/rules/narrative/narrative-antipatterns.ts"() {
|
|
3789
3792
|
"use strict";
|
|
3790
3793
|
init_models();
|
|
3794
|
+
byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3791
3795
|
narrativeAntipatternsRule = {
|
|
3792
3796
|
name: "narrative-antipatterns",
|
|
3793
3797
|
description: "Provable narrative bugs, caught at spec level before implementation: a step-graph cycle with no exit edge and no return/throw member never terminates by construction (INESCAPABLE_CYCLE); a branch or switch whose arms all target the same step decides nothing (MEANINGLESS_BRANCH); a method-level call cycle across components in which every call edge is unavoidable on every entry-to-exit path is unbounded recursion (UNCONDITIONAL_CALL_CYCLE \u2014 dispatch steps resolved through their portal tables; a cycle with even one guarded edge is NOT flagged, since prose conditions are never judged). Structure-only claims \u2014 warnings, lint.allow-suppressible.",
|
|
@@ -3806,29 +3810,33 @@ var init_narrative_antipatterns = __esm({
|
|
|
3806
3810
|
const steps = implMethod.narrative;
|
|
3807
3811
|
if (!steps.length) continue;
|
|
3808
3812
|
const where = `Method "${implMethod.name}" in implementation "${impl.id}": `;
|
|
3809
|
-
const graph =
|
|
3813
|
+
const graph = completedStepGraph(steps);
|
|
3814
|
+
const decisionTargets = (stepNumber, explicit, fallThroughField) => {
|
|
3815
|
+
const targets = new Set(explicit);
|
|
3816
|
+
if (fallThroughField !== void 0) targets.add(fallThroughField);
|
|
3817
|
+
else for (const t of graph.successorsOf(stepNumber)) targets.add(t);
|
|
3818
|
+
return [...targets];
|
|
3819
|
+
};
|
|
3810
3820
|
for (const s of steps) {
|
|
3811
3821
|
if (s.type === "branch" && s.onFalseStep !== void 0) {
|
|
3812
|
-
const
|
|
3813
|
-
if (
|
|
3822
|
+
const targets = decisionTargets(s.stepNumber, [s.onFalseStep], s.onTrueStep);
|
|
3823
|
+
if (targets.length === 1) {
|
|
3814
3824
|
ctx.addIssue(
|
|
3815
3825
|
"warning",
|
|
3816
3826
|
"MEANINGLESS_BRANCH",
|
|
3817
|
-
`${where}branch step ${s.stepNumber} sends both arms to step ${
|
|
3827
|
+
`${where}branch step ${s.stepNumber} sends both arms to step ${targets[0]} \u2014 the condition decides nothing. Point the arms at different steps, or replace the branch with a local step.`,
|
|
3818
3828
|
impl.id,
|
|
3819
3829
|
isDraftCtx
|
|
3820
3830
|
);
|
|
3821
3831
|
}
|
|
3822
3832
|
}
|
|
3823
3833
|
if (s.type === "switch" && s.cases?.length) {
|
|
3824
|
-
const targets =
|
|
3825
|
-
|
|
3826
|
-
if (def !== void 0) targets.add(def);
|
|
3827
|
-
if (targets.size === 1) {
|
|
3834
|
+
const targets = decisionTargets(s.stepNumber, s.cases.map((c) => c.step), s.defaultStep);
|
|
3835
|
+
if (targets.length === 1) {
|
|
3828
3836
|
ctx.addIssue(
|
|
3829
3837
|
"warning",
|
|
3830
3838
|
"MEANINGLESS_BRANCH",
|
|
3831
|
-
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${
|
|
3839
|
+
`${where}switch step ${s.stepNumber} sends every case (and the default) to step ${targets[0]} \u2014 the dispatch decides nothing.`,
|
|
3832
3840
|
impl.id,
|
|
3833
3841
|
isDraftCtx
|
|
3834
3842
|
);
|
|
@@ -3901,8 +3909,8 @@ var init_narrative_antipatterns = __esm({
|
|
|
3901
3909
|
if (!isCycle) continue;
|
|
3902
3910
|
const memberEdges = keys.flatMap((k) => (adjacency.get(k) ?? []).filter((e) => inScc.has(e.toKey)));
|
|
3903
3911
|
if (memberEdges.length === 0) continue;
|
|
3904
|
-
const anchor = [...memberEdges].sort((a, b) => a.fromKey.
|
|
3905
|
-
const path70 = [...keys].sort().join(" \u2192 ");
|
|
3912
|
+
const anchor = [...memberEdges].sort((a, b) => byCodeUnit(a.fromKey, b.fromKey) || byCodeUnit(a.impl.id, b.impl.id) || a.stepNumber - b.stepNumber)[0];
|
|
3913
|
+
const path70 = [...keys].sort(byCodeUnit).join(" \u2192 ");
|
|
3906
3914
|
ctx.addIssue(
|
|
3907
3915
|
"warning",
|
|
3908
3916
|
"UNCONDITIONAL_CALL_CYCLE",
|
|
@@ -4154,14 +4162,15 @@ var init_portal_endpoints = __esm({
|
|
|
4154
4162
|
continue;
|
|
4155
4163
|
}
|
|
4156
4164
|
for (const intf of compInterfaces) {
|
|
4165
|
+
const isIntfDraft = intf.status === "draft" || intf.status === "design";
|
|
4157
4166
|
for (const m of intf.methods) {
|
|
4158
4167
|
if (m.endpoint) {
|
|
4159
4168
|
ctx.addIssue(
|
|
4160
4169
|
"error",
|
|
4161
4170
|
"ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT",
|
|
4162
4171
|
`Architectural violation: Component "${comp.id}" is a ${comp.componentType}, but method "${m.name}" on its interface "${intf.id}" declares an endpoint. Only Portal components may carry endpoints.`,
|
|
4163
|
-
|
|
4164
|
-
isDraftCtx
|
|
4172
|
+
intf.id,
|
|
4173
|
+
isDraftCtx || isIntfDraft
|
|
4165
4174
|
);
|
|
4166
4175
|
}
|
|
4167
4176
|
}
|
|
@@ -4292,7 +4301,7 @@ var init_stereotype_dependencies = __esm({
|
|
|
4292
4301
|
{ code: "ARCHITECTURE_VIOLATION_ADAPTER_DEP", defaultSeverity: "error", summary: "Adapter depending on Orchestrators or Stores" },
|
|
4293
4302
|
{ code: "ARCHITECTURE_VIOLATION_INDEX_DEP", defaultSeverity: "error", summary: "Index depending on anything but its Store or an Adapter" },
|
|
4294
4303
|
{ code: "ARCHITECTURE_VIOLATION_VIEW_DEP", defaultSeverity: "error", summary: "View depending on logic/persistence layers" },
|
|
4295
|
-
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative
|
|
4304
|
+
{ code: "PORTAL_WRITE_SHORTCUT", defaultSeverity: "error", summary: "Portal narrative call or dispatch-table binding reaches a write-effect method on a Repository/Index directly \u2014 reads may shortcut, writes route through an Orchestrator (judged on effect-tagged facade methods; untagged methods are not yet judged)" }
|
|
4296
4305
|
],
|
|
4297
4306
|
check(ctx) {
|
|
4298
4307
|
for (const comp of ctx.components) {
|
|
@@ -4364,10 +4373,11 @@ var init_stereotype_dependencies = __esm({
|
|
|
4364
4373
|
crossDraft
|
|
4365
4374
|
);
|
|
4366
4375
|
} else if (depComp.componentType !== "Portal" && depComp.componentType !== "Gateway") {
|
|
4376
|
+
const crosser = comp.componentType === "Adapter" ? "client Adapter" : comp.componentType;
|
|
4367
4377
|
ctx.addIssue(
|
|
4368
4378
|
"error",
|
|
4369
4379
|
"CROSS_SUBSYSTEM_TARGET_NON_PORTAL",
|
|
4370
|
-
`Boundary violation:
|
|
4380
|
+
`Boundary violation: ${crosser} "${comp.id}" enters subsystem "${depComp.subsystem}" through "${depComp.id}" (${depComp.componentType}), not its inbound Portal. A cross-subsystem hop must target the remote subsystem's Portal (its front door), which dispatches inward \u2014 publishing/depending on an internal ${depComp.componentType} leaks the boundary and breaks the distribution seam. Expose a Portal for "${depComp.subsystem}" and point "${comp.id}" at it.`,
|
|
4371
4381
|
comp.id,
|
|
4372
4382
|
crossDraft
|
|
4373
4383
|
);
|
|
@@ -4491,6 +4501,23 @@ var init_stereotype_dependencies = __esm({
|
|
|
4491
4501
|
}
|
|
4492
4502
|
}
|
|
4493
4503
|
}
|
|
4504
|
+
for (const comp of ctx.components) {
|
|
4505
|
+
if (comp.componentType !== "Portal" || !comp.dispatch || comp.dispatch.length === 0) continue;
|
|
4506
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
4507
|
+
for (const binding of comp.dispatch) {
|
|
4508
|
+
const target = ctx.componentMap.get(binding.component);
|
|
4509
|
+
if (!target || target.componentType !== "Repository" && target.componentType !== "Index") continue;
|
|
4510
|
+
const targetMethod = ctx.interfaceMethodsOf(target.id).find((m) => m.name === binding.method);
|
|
4511
|
+
if (targetMethod?.effect !== "write") continue;
|
|
4512
|
+
ctx.addIssue(
|
|
4513
|
+
"error",
|
|
4514
|
+
"PORTAL_WRITE_SHORTCUT",
|
|
4515
|
+
`Portal "${comp.id}": dispatch binding "${binding.capability}" routes to write-effect method ${target.id}.${binding.method} directly. The Portal\u2192${target.componentType} shortcut is licensed for READS only \u2014 route the write through an Orchestrator that owns the workflow.`,
|
|
4516
|
+
comp.id,
|
|
4517
|
+
isDraftCtx || ctx.isComponentDraft(target.id)
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4494
4521
|
}
|
|
4495
4522
|
};
|
|
4496
4523
|
}
|
|
@@ -4514,7 +4541,7 @@ var init_pattern_ownership = __esm({
|
|
|
4514
4541
|
{ code: "REPOSITORY_CONTAINMENT", defaultSeverity: "error", summary: "Repository owning a non Store/Registry/Index/Adapter member" },
|
|
4515
4542
|
{ code: "GATEWAY_CONTAINMENT", defaultSeverity: "error", summary: "Gateway owning a non Portal/Orchestrator/Specialist member" },
|
|
4516
4543
|
{ code: "FEATURE_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "FeatureComponent not owning exactly one Orchestrator + one or more Views" },
|
|
4517
|
-
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent
|
|
4544
|
+
{ code: "ROUTER_COMPONENT_CONTAINMENT", defaultSeverity: "error", summary: "RouterComponent not owning exactly one Portal facade, or owning no children to route to" },
|
|
4518
4545
|
{ code: "VISIBILITY_VIOLATION", defaultSeverity: "error", summary: "Dependency on a block privately owned by another pattern" },
|
|
4519
4546
|
{ code: "UNOWNED_STORE", defaultSeverity: "warning", summary: "Store not owned by any pattern \u2014 recommended shape is a Repository; a deliberate standalone Store needs a lint.allow" },
|
|
4520
4547
|
{ code: "REGISTRY_WITHOUT_STORE", defaultSeverity: "warning", summary: "Standalone Registry with no Store to write to \u2014 either mistyped (a fused file-backed store belongs typed Store) or orphaned" }
|
|
@@ -4536,14 +4563,15 @@ var init_pattern_ownership = __esm({
|
|
|
4536
4563
|
ctx.addIssue("error", "INVALID_OWNED_MEMBER", `Component "${comp.id}" owns "${memberId}" which does not exist.`, comp.id, isDraftCtx);
|
|
4537
4564
|
continue;
|
|
4538
4565
|
}
|
|
4566
|
+
if (!pattern) continue;
|
|
4539
4567
|
if (isPattern(member)) {
|
|
4540
4568
|
ctx.addIssue("error", "PATTERN_OWNS_PATTERN", `Pattern "${comp.id}" owns "${memberId}", which is itself a pattern. Patterns own only building blocks \u2014 compose patterns at the subsystem (L1) level.`, comp.id, isDraftCtx);
|
|
4541
4569
|
}
|
|
4542
|
-
const
|
|
4543
|
-
if (
|
|
4544
|
-
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${
|
|
4570
|
+
const firstOwner = ownedBy.get(memberId);
|
|
4571
|
+
if (firstOwner && firstOwner !== comp.id) {
|
|
4572
|
+
ctx.addIssue("error", "SHARED_OWNED_MEMBER", `Block "${memberId}" is owned by both "${firstOwner}" and "${comp.id}"; a block has exactly one owner.`, comp.id, isDraftCtx);
|
|
4545
4573
|
}
|
|
4546
|
-
ownedBy.set(memberId, comp.id);
|
|
4574
|
+
if (!firstOwner) ownedBy.set(memberId, comp.id);
|
|
4547
4575
|
}
|
|
4548
4576
|
if (comp.componentType === "Repository") {
|
|
4549
4577
|
const allowed = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter"]);
|
|
@@ -4578,14 +4606,14 @@ var init_pattern_ownership = __esm({
|
|
|
4578
4606
|
}
|
|
4579
4607
|
}
|
|
4580
4608
|
if (comp.componentType === "RouterComponent") {
|
|
4581
|
-
let
|
|
4609
|
+
let portals = 0;
|
|
4582
4610
|
let hasChildren = false;
|
|
4583
4611
|
for (const memberId of comp.owns) {
|
|
4584
4612
|
const t = ctx.componentMap.get(memberId)?.componentType;
|
|
4585
|
-
if (t === "Portal")
|
|
4613
|
+
if (t === "Portal") portals++;
|
|
4586
4614
|
else if (t) hasChildren = true;
|
|
4587
4615
|
}
|
|
4588
|
-
if (
|
|
4616
|
+
if (portals !== 1) {
|
|
4589
4617
|
ctx.addIssue("error", "ROUTER_COMPONENT_CONTAINMENT", `RouterComponent "${comp.id}" must own exactly one Portal component to act as its facade.`, comp.id, isDraftCtx);
|
|
4590
4618
|
}
|
|
4591
4619
|
if (!hasChildren) {
|
|
@@ -5087,54 +5115,43 @@ var init_dependency_cycles = __esm({
|
|
|
5087
5115
|
"use strict";
|
|
5088
5116
|
cyclesRule = {
|
|
5089
5117
|
name: "dependency-cycles",
|
|
5090
|
-
description: "The component dependsOn graph must be a DAG; the first
|
|
5118
|
+
description: "The component dependsOn graph must be a DAG; the first cycle through a component in the run's scope is reported with its full path.",
|
|
5091
5119
|
codes: [
|
|
5092
5120
|
{ code: "CIRCULAR_DEPENDENCY", defaultSeverity: "error", summary: "Circular dependency between components" }
|
|
5093
5121
|
],
|
|
5094
5122
|
check(ctx) {
|
|
5095
|
-
const
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
const comp = ctx.componentMap.get(compId);
|
|
5102
|
-
if (comp) {
|
|
5103
|
-
for (const depId of comp.dependsOn) {
|
|
5104
|
-
if (!visited.has(depId)) {
|
|
5105
|
-
if (dfs(depId, pathTrace)) {
|
|
5106
|
-
recStack.delete(compId);
|
|
5107
|
-
pathTrace.pop();
|
|
5108
|
-
return true;
|
|
5109
|
-
}
|
|
5110
|
-
} else if (recStack.has(depId)) {
|
|
5123
|
+
const cycleFrom = (startId) => {
|
|
5124
|
+
const visited = /* @__PURE__ */ new Set([startId]);
|
|
5125
|
+
const pathTrace = [startId];
|
|
5126
|
+
const search = (compId) => {
|
|
5127
|
+
for (const depId of ctx.componentMap.get(compId)?.dependsOn ?? []) {
|
|
5128
|
+
if (depId === startId) {
|
|
5111
5129
|
pathTrace.push(depId);
|
|
5112
|
-
const cyclePath = pathTrace.slice(pathTrace.indexOf(depId)).join(" -> ");
|
|
5113
|
-
const isDraftCtx = ctx.isComponentDraft(compId) || ctx.isComponentDraft(depId);
|
|
5114
|
-
ctx.addIssue(
|
|
5115
|
-
"error",
|
|
5116
|
-
"CIRCULAR_DEPENDENCY",
|
|
5117
|
-
`Circular dependency detected: ${cyclePath}`,
|
|
5118
|
-
compId,
|
|
5119
|
-
isDraftCtx
|
|
5120
|
-
);
|
|
5121
|
-
pathTrace.pop();
|
|
5122
|
-
recStack.delete(compId);
|
|
5123
|
-
pathTrace.pop();
|
|
5124
5130
|
return true;
|
|
5125
5131
|
}
|
|
5132
|
+
if (visited.has(depId)) continue;
|
|
5133
|
+
visited.add(depId);
|
|
5134
|
+
pathTrace.push(depId);
|
|
5135
|
+
if (search(depId)) return true;
|
|
5136
|
+
pathTrace.pop();
|
|
5126
5137
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
return false;
|
|
5138
|
+
return false;
|
|
5139
|
+
};
|
|
5140
|
+
return search(startId) ? pathTrace : void 0;
|
|
5131
5141
|
};
|
|
5132
5142
|
for (const comp of ctx.components) {
|
|
5133
|
-
if (!
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5143
|
+
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5144
|
+
const cycle = cycleFrom(comp.id);
|
|
5145
|
+
if (!cycle) continue;
|
|
5146
|
+
const closing = cycle[cycle.length - 2];
|
|
5147
|
+
ctx.addIssue(
|
|
5148
|
+
"error",
|
|
5149
|
+
"CIRCULAR_DEPENDENCY",
|
|
5150
|
+
`Circular dependency detected: ${cycle.join(" -> ")}`,
|
|
5151
|
+
comp.id,
|
|
5152
|
+
ctx.isComponentDraft(comp.id) || ctx.isComponentDraft(closing)
|
|
5153
|
+
);
|
|
5154
|
+
return;
|
|
5138
5155
|
}
|
|
5139
5156
|
}
|
|
5140
5157
|
};
|
|
@@ -5263,19 +5280,18 @@ var init_dispatch_tables = __esm({
|
|
|
5263
5280
|
);
|
|
5264
5281
|
continue;
|
|
5265
5282
|
}
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
}
|
|
5283
|
+
const boundMethod = ctx.interfaceMethodsOf(binding.component).find((m) => m.name === binding.method);
|
|
5284
|
+
if (!boundMethod) continue;
|
|
5285
|
+
const declared = new Set(boundMethod.guarantees ?? []);
|
|
5286
|
+
for (const g of step.assertsGuarantees ?? []) {
|
|
5287
|
+
if (!declared.has(g)) {
|
|
5288
|
+
ctx.addIssue(
|
|
5289
|
+
"warning",
|
|
5290
|
+
"NARRATIVE_SEMANTIC_UNBACKED",
|
|
5291
|
+
`${where} asserts guarantee "${g}", but capability "${step.capability}" resolves to "${binding.component}.${binding.method}", which does not list "${g}" among its L3 contract guarantees. Declare it there (and ensure its shape can deliver it), or revise the narrative.`,
|
|
5292
|
+
impl.id,
|
|
5293
|
+
isDraftCtx || ctx.isComponentDraft(binding.component)
|
|
5294
|
+
);
|
|
5279
5295
|
}
|
|
5280
5296
|
}
|
|
5281
5297
|
}
|
|
@@ -5480,12 +5496,11 @@ var init_unused_detection = __esm({
|
|
|
5480
5496
|
const invokedBySeeds = [];
|
|
5481
5497
|
for (const intf of ctx.interfaces) {
|
|
5482
5498
|
if (!ctx.componentMap.has(intf.component)) continue;
|
|
5483
|
-
const comp = ctx.componentMap.get(intf.component);
|
|
5484
5499
|
for (const m of intf.methods) {
|
|
5485
5500
|
if (!m.invokedBy) continue;
|
|
5486
5501
|
invokedBySeeds.push({ compId: intf.component, methodName: m.name });
|
|
5487
5502
|
if (!ctx.isSpecInScope(intf.id)) continue;
|
|
5488
|
-
const isDraftCtx =
|
|
5503
|
+
const isDraftCtx = ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design";
|
|
5489
5504
|
if (!passesIntentFloor(m.invokedBy.caller, m.name)) {
|
|
5490
5505
|
ctx.addIssue(
|
|
5491
5506
|
"warning",
|
|
@@ -5510,7 +5525,7 @@ var init_unused_detection = __esm({
|
|
|
5510
5525
|
for (const comp of ctx.components) {
|
|
5511
5526
|
if (!ctx.isSpecInScope(comp.id)) continue;
|
|
5512
5527
|
if (!reach.reachesComponent(comp.id)) {
|
|
5513
|
-
const isDraftCtx =
|
|
5528
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id);
|
|
5514
5529
|
ctx.addIssue(
|
|
5515
5530
|
"warning",
|
|
5516
5531
|
"UNUSED_COMPONENT",
|
|
@@ -5522,12 +5537,12 @@ var init_unused_detection = __esm({
|
|
|
5522
5537
|
for (const intf of ctx.interfacesByComponent.get(comp.id) ?? []) {
|
|
5523
5538
|
for (const m of intf.methods) {
|
|
5524
5539
|
if (!reach.reachesMethod(comp.id, m.name)) {
|
|
5525
|
-
const isDraftCtx =
|
|
5540
|
+
const isDraftCtx = ctx.isComponentDraft(comp.id) || intf.status === "draft" || intf.status === "design";
|
|
5526
5541
|
ctx.addIssue(
|
|
5527
5542
|
"warning",
|
|
5528
5543
|
"UNUSED_METHOD",
|
|
5529
5544
|
`Method "${m.name}" on component "${comp.id}" is defined but never called by any narrative step.`,
|
|
5530
|
-
|
|
5545
|
+
intf.id,
|
|
5531
5546
|
isDraftCtx
|
|
5532
5547
|
);
|
|
5533
5548
|
}
|
|
@@ -5638,20 +5653,20 @@ var init_durability_round_trip = __esm({
|
|
|
5638
5653
|
});
|
|
5639
5654
|
|
|
5640
5655
|
// src/core/rules/wiring/untyped-seams.ts
|
|
5641
|
-
function
|
|
5642
|
-
|
|
5643
|
-
return (m ? m[1] : typeRef).trim();
|
|
5656
|
+
function namesBareType(refs) {
|
|
5657
|
+
return refs.some((ref) => BARE_SEAM_TYPES.has(ref.toLowerCase()));
|
|
5644
5658
|
}
|
|
5645
5659
|
var BARE_SEAM_TYPES, untypedSeamRule;
|
|
5646
5660
|
var init_untyped_seams = __esm({
|
|
5647
5661
|
"src/core/rules/wiring/untyped-seams.ts"() {
|
|
5648
5662
|
"use strict";
|
|
5663
|
+
init_models();
|
|
5649
5664
|
BARE_SEAM_TYPES = /* @__PURE__ */ new Set(["json", "any", "unknown", "object"]);
|
|
5650
5665
|
untypedSeamRule = {
|
|
5651
5666
|
name: "untyped-seams",
|
|
5652
|
-
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown
|
|
5667
|
+
description: "Methods on a subsystem's published components (its public surface) should not take or return bare Json/any/unknown/object, judged through each method's type references so a prose signature is judged like structured params: cross-subsystem contracts are the swap seam and must be typed. Generic-dispatch portals carry per-capability types via their dispatch table instead.",
|
|
5653
5668
|
codes: [
|
|
5654
|
-
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown parameter or return crossing a subsystem public surface" }
|
|
5669
|
+
{ code: "UNTYPED_SEAM", defaultSeverity: "warning", summary: "Bare Json/any/unknown/object parameter or return crossing a subsystem public surface" }
|
|
5655
5670
|
],
|
|
5656
5671
|
check(ctx) {
|
|
5657
5672
|
for (const sub of ctx.subsystems) {
|
|
@@ -5665,14 +5680,17 @@ var init_untyped_seams = __esm({
|
|
|
5665
5680
|
const isDraftCtx = ctx.isComponentDraft(compId) || intf.status === "draft" || intf.status === "design";
|
|
5666
5681
|
for (const m of intf.methods) {
|
|
5667
5682
|
const offenders = [];
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5683
|
+
if (m.params && m.params.length > 0) {
|
|
5684
|
+
for (const p of m.params) {
|
|
5685
|
+
if (namesBareType(extractTypeIdentifiers(p.type))) {
|
|
5686
|
+
offenders.push(`param "${p.name}: ${p.type}"`);
|
|
5687
|
+
}
|
|
5671
5688
|
}
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5689
|
+
if (namesBareType(extractTypeIdentifiers(m.returns ?? ""))) {
|
|
5690
|
+
offenders.push(`return "${m.returns}"`);
|
|
5691
|
+
}
|
|
5692
|
+
} else if (namesBareType(methodTypeRefs(m))) {
|
|
5693
|
+
offenders.push(`signature "${m.signature}"`);
|
|
5676
5694
|
}
|
|
5677
5695
|
if (offenders.length) {
|
|
5678
5696
|
ctx.addIssue(
|
|
@@ -5697,15 +5715,19 @@ function componentOfImpl(ctx, implContract) {
|
|
|
5697
5715
|
const contract = ctx.interfaceMap.get(implContract);
|
|
5698
5716
|
return contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
5699
5717
|
}
|
|
5700
|
-
|
|
5718
|
+
function withoutQuotedText(text2) {
|
|
5719
|
+
return text2.replace(QUOTED_SPAN, " ");
|
|
5720
|
+
}
|
|
5721
|
+
var DATA_STEREOTYPES, CLAIM_PHRASES, QUOTED_SPAN, proseClaimRule;
|
|
5701
5722
|
var init_prose_claims = __esm({
|
|
5702
5723
|
"src/core/rules/wiring/prose-claims.ts"() {
|
|
5703
5724
|
"use strict";
|
|
5704
5725
|
DATA_STEREOTYPES = /* @__PURE__ */ new Set(["Store", "Registry", "Index", "Adapter", "Repository"]);
|
|
5705
|
-
CLAIM_PHRASES = /\b(persist(s|ed|ent)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
|
|
5726
|
+
CLAIM_PHRASES = /\b(persist(s|ed|ent|ing|ence)?|survives?\s+(a\s+)?restart|writ(es?|ten)\s+to\s+disk|registered\s+into|durabl[ey])\b/i;
|
|
5727
|
+
QUOTED_SPAN = /"[^"]*"|“[^”]*”|`[^`]*`|(?<![\p{L}\p{N}])['‘](?:[^'‘’]|(?<=[\p{L}\p{N}])['’](?=[\p{L}\p{N}]))*['’](?![\p{L}\p{N}])/gu;
|
|
5706
5728
|
proseClaimRule = {
|
|
5707
5729
|
name: "prose-claims",
|
|
5708
|
-
description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "survives restart", "registered into") on a logic component whose narrative has no call
|
|
5730
|
+
description: 'Flags durability/side-effect claims that exist only in prose: a local step description or an intent paragraph claiming persistence ("persisted", "persistence", "survives restart", "registered into") on a logic component whose narrative has no call, register or dispatch edge to any data-layer component (Store/Registry/Index/Adapter/Repository). Text inside quotes or backticks names a value or quotes a message and claims nothing. Data-layer components are exempt \u2014 they ARE the persistence.',
|
|
5709
5731
|
codes: [
|
|
5710
5732
|
{ code: "UNREALIZED_CLAIM", defaultSeverity: "warning", summary: "Durability/side-effect claim in prose with no matching structural edge" }
|
|
5711
5733
|
],
|
|
@@ -5716,11 +5738,11 @@ var init_prose_claims = __esm({
|
|
|
5716
5738
|
if (DATA_STEREOTYPES.has(comp.componentType)) continue;
|
|
5717
5739
|
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5718
5740
|
for (const implMethod of impl.methods) {
|
|
5719
|
-
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(step.description) })).filter((c) => c.claim !== null);
|
|
5720
|
-
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(implMethod.intent) : null;
|
|
5741
|
+
const stepClaims = implMethod.narrative.filter((step) => step.type === "local").map((step) => ({ step, claim: CLAIM_PHRASES.exec(withoutQuotedText(step.description)) })).filter((c) => c.claim !== null);
|
|
5742
|
+
const intentClaim = implMethod.intent ? CLAIM_PHRASES.exec(withoutQuotedText(implMethod.intent)) : null;
|
|
5721
5743
|
if (!stepClaims.length && !intentClaim) continue;
|
|
5722
5744
|
const hasDataEdge = implMethod.narrative.some((step) => {
|
|
5723
|
-
if (step.type !== "call" && step.type !== "dispatch") return false;
|
|
5745
|
+
if (step.type !== "call" && step.type !== "register" && step.type !== "dispatch") return false;
|
|
5724
5746
|
if (!step.targetComponent) return false;
|
|
5725
5747
|
const target = ctx.componentMap.get(step.targetComponent);
|
|
5726
5748
|
if (target && DATA_STEREOTYPES.has(target.componentType)) return true;
|
|
@@ -5733,7 +5755,7 @@ var init_prose_claims = __esm({
|
|
|
5733
5755
|
ctx.addIssue(
|
|
5734
5756
|
"warning",
|
|
5735
5757
|
"UNREALIZED_CLAIM",
|
|
5736
|
-
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" claims "${claim[0]}" but no call/dispatch edge in this narrative reaches a Store/Registry/Index/Adapter \u2014 realize the claim as a structural edge (and durability tags), or reword the prose.`,
|
|
5758
|
+
`Step ${step.stepNumber} of "${implMethod.name}" in implementation "${impl.id}" claims "${claim[0]}" but no call/register/dispatch edge in this narrative reaches a Store/Registry/Index/Adapter \u2014 realize the claim as a structural edge (and durability tags), or reword the prose.`,
|
|
5737
5759
|
impl.id,
|
|
5738
5760
|
isDraftCtx
|
|
5739
5761
|
);
|
|
@@ -5792,6 +5814,10 @@ function stepAsserts(impl, methodName, type, invariantId) {
|
|
|
5792
5814
|
(step) => (step.assertsInvariants ?? []).some((ref) => refMatchesInvariant(ref, type, invariantId))
|
|
5793
5815
|
);
|
|
5794
5816
|
}
|
|
5817
|
+
function isEntityDraft(t, ctx) {
|
|
5818
|
+
const sub = t.subsystem ? ctx.subsystems.find((s) => s.id === t.subsystem) : void 0;
|
|
5819
|
+
return sub !== void 0 && isDraftSubsystem(sub);
|
|
5820
|
+
}
|
|
5795
5821
|
function resolveComponentClass(t, ctx) {
|
|
5796
5822
|
if (!t.componentClass) return void 0;
|
|
5797
5823
|
const direct = ctx.componentMap.get(t.componentClass);
|
|
@@ -5818,6 +5844,7 @@ var init_invariant_backing = __esm({
|
|
|
5818
5844
|
for (const t of ctx.types) {
|
|
5819
5845
|
const invariants = t.invariants ?? [];
|
|
5820
5846
|
if (invariants.length === 0) continue;
|
|
5847
|
+
const entityDraft = isEntityDraft(t, ctx);
|
|
5821
5848
|
const seen = /* @__PURE__ */ new Set();
|
|
5822
5849
|
for (const inv of invariants) {
|
|
5823
5850
|
if (seen.has(inv.id)) {
|
|
@@ -5825,7 +5852,8 @@ var init_invariant_backing = __esm({
|
|
|
5825
5852
|
"error",
|
|
5826
5853
|
"DUPLICATE_INVARIANT_ID",
|
|
5827
5854
|
`Entity "${t.id}" declares invariant id "${inv.id}" more than once \u2014 invariant ids must be unique within the entity.`,
|
|
5828
|
-
t.id
|
|
5855
|
+
t.id,
|
|
5856
|
+
entityDraft
|
|
5829
5857
|
);
|
|
5830
5858
|
}
|
|
5831
5859
|
seen.add(inv.id);
|
|
@@ -5836,7 +5864,8 @@ var init_invariant_backing = __esm({
|
|
|
5836
5864
|
"warning",
|
|
5837
5865
|
"INVARIANT_UNANCHORED",
|
|
5838
5866
|
`Entity "${t.id}" declares ${invariants.length} invariant(s) but ${t.componentClass ? `its componentClass "${t.componentClass}" does not resolve to a component` : "has no componentClass"} \u2014 without an owning component there is no write path to hold the invariant against. Link the lifecycle owner via componentClass.`,
|
|
5839
|
-
t.id
|
|
5867
|
+
t.id,
|
|
5868
|
+
entityDraft
|
|
5840
5869
|
);
|
|
5841
5870
|
continue;
|
|
5842
5871
|
}
|
|
@@ -5850,13 +5879,13 @@ var init_invariant_backing = __esm({
|
|
|
5850
5879
|
"INVARIANT_UNANCHORED",
|
|
5851
5880
|
`Entity "${t.id}" declares ${invariants.length} invariant(s) anchored to "${comp.id}", but none of that component's contract methods declare effect: write \u2014 the validator cannot identify the write paths that must assert them. Tag the mutating methods with effect: write.`,
|
|
5852
5881
|
t.id,
|
|
5853
|
-
ctx.isComponentDraft(comp.id)
|
|
5882
|
+
entityDraft || ctx.isComponentDraft(comp.id)
|
|
5854
5883
|
);
|
|
5855
5884
|
continue;
|
|
5856
5885
|
}
|
|
5857
5886
|
for (const { intf, method: method2 } of writeMethods) {
|
|
5858
5887
|
for (const impl of ctx.implementationsByContract.get(intf.id) ?? []) {
|
|
5859
|
-
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
5888
|
+
const isDraftCtx = entityDraft || ctx.isImplementationDraft(impl);
|
|
5860
5889
|
for (const inv of invariants) {
|
|
5861
5890
|
if (stepAsserts(impl, method2.name, t, inv.id)) continue;
|
|
5862
5891
|
ctx.addIssue(
|
|
@@ -5894,30 +5923,35 @@ var init_invariant_backing = __esm({
|
|
|
5894
5923
|
|
|
5895
5924
|
// src/core/rules/wiring/event-topology.ts
|
|
5896
5925
|
function collectEnds(ctx) {
|
|
5897
|
-
const emitters =
|
|
5898
|
-
const subscribers =
|
|
5926
|
+
const emitters = /* @__PURE__ */ new Map();
|
|
5927
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
5928
|
+
const addEnd = (ends, topic, compId, via) => {
|
|
5929
|
+
const key = `${compId}#${topic}`;
|
|
5930
|
+
const end = ends.get(key);
|
|
5931
|
+
if (!end) ends.set(key, { topic, compId, vias: [via] });
|
|
5932
|
+
else if (!end.vias.includes(via)) end.vias.push(via);
|
|
5933
|
+
};
|
|
5899
5934
|
for (const comp of ctx.components) {
|
|
5900
5935
|
for (const e of comp.emits ?? []) {
|
|
5901
|
-
emitters
|
|
5936
|
+
addEnd(emitters, e.topic, comp.id, "emits declaration");
|
|
5902
5937
|
}
|
|
5903
5938
|
for (const s of comp.subscribesTo ?? []) {
|
|
5904
|
-
subscribers
|
|
5939
|
+
addEnd(subscribers, s.topic, comp.id, "subscribesTo declaration");
|
|
5905
5940
|
}
|
|
5906
5941
|
}
|
|
5907
5942
|
for (const intf of ctx.interfaces) {
|
|
5908
5943
|
for (const method2 of intf.methods) {
|
|
5909
5944
|
const ep = method2.endpoint;
|
|
5910
5945
|
if (!ep || ep.transport !== "MessageBus") continue;
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
else subscribers.push(end);
|
|
5946
|
+
addEnd(
|
|
5947
|
+
ep.direction === "publish" ? emitters : subscribers,
|
|
5948
|
+
ep.topic,
|
|
5949
|
+
intf.component,
|
|
5950
|
+
`MessageBus endpoint on ${intf.id}.${method2.name}`
|
|
5951
|
+
);
|
|
5918
5952
|
}
|
|
5919
5953
|
}
|
|
5920
|
-
return { emitters, subscribers };
|
|
5954
|
+
return { emitters: [...emitters.values()], subscribers: [...subscribers.values()] };
|
|
5921
5955
|
}
|
|
5922
5956
|
var eventTopologyRule;
|
|
5923
5957
|
var init_event_topology = __esm({
|
|
@@ -5925,7 +5959,7 @@ var init_event_topology = __esm({
|
|
|
5925
5959
|
"use strict";
|
|
5926
5960
|
eventTopologyRule = {
|
|
5927
5961
|
name: "event-topology",
|
|
5928
|
-
description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
|
|
5962
|
+
description: "Bipartite completeness of the declared pub/sub graph: every topic a component emits (emits declarations, MessageBus publish endpoints) must have at least one subscriber (subscribesTo declarations, MessageBus subscribe endpoints), and every subscription must have at least one source. Pairing is by exact topic name, and a topic is reported once per component however many declarations bind it there. Trees that declare no event edges see nothing \u2014 the rule costs request/response systems zero noise. Warnings + lint.allow: a topic produced for (or consumed from) an EXTERNAL system is a legitimate, declarable exception.",
|
|
5929
5963
|
codes: [
|
|
5930
5964
|
{ code: "UNCONSUMED_TOPIC", defaultSeverity: "warning", summary: "Topic is emitted but nothing in the tree subscribes to it" },
|
|
5931
5965
|
{ code: "UNSOURCED_SUBSCRIPTION", defaultSeverity: "warning", summary: "Topic is subscribed to but nothing in the tree emits it" }
|
|
@@ -5940,7 +5974,7 @@ var init_event_topology = __esm({
|
|
|
5940
5974
|
ctx.addIssue(
|
|
5941
5975
|
"warning",
|
|
5942
5976
|
"UNCONSUMED_TOPIC",
|
|
5943
|
-
`Component "${e.compId}" emits topic "${e.topic}" (${e.
|
|
5977
|
+
`Component "${e.compId}" emits topic "${e.topic}" (${e.vias.join(", ")}), but nothing in this tree subscribes to it \u2014 the event goes nowhere. Wire a subscriber (subscribesTo, or a MessageBus subscribe endpoint), fix the topic name, or lint.allow with the external consumer named.`,
|
|
5944
5978
|
e.compId,
|
|
5945
5979
|
ctx.isComponentDraft(e.compId)
|
|
5946
5980
|
);
|
|
@@ -5950,7 +5984,7 @@ var init_event_topology = __esm({
|
|
|
5950
5984
|
ctx.addIssue(
|
|
5951
5985
|
"warning",
|
|
5952
5986
|
"UNSOURCED_SUBSCRIPTION",
|
|
5953
|
-
`Component "${s.compId}" subscribes to topic "${s.topic}" (${s.
|
|
5987
|
+
`Component "${s.compId}" subscribes to topic "${s.topic}" (${s.vias.join(", ")}), but nothing in this tree emits it \u2014 the handler can never fire from inside this system. Wire the emitter (emits, or a MessageBus publish endpoint), fix the topic name, or lint.allow with the external source named.`,
|
|
5954
5988
|
s.compId,
|
|
5955
5989
|
ctx.isComponentDraft(s.compId)
|
|
5956
5990
|
);
|
|
@@ -5969,13 +6003,13 @@ var init_structural_conformance = __esm({
|
|
|
5969
6003
|
quoteList = (names) => names.map((n) => `"${n}"`).join(", ");
|
|
5970
6004
|
structuralConformanceRule = {
|
|
5971
6005
|
name: "structural-conformance",
|
|
5972
|
-
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier (declared | anchored | off; Portals default to anchored, everything else to declared; per-method `symbol` maps intent-language names to code names), and every finding code a contract method declares must
|
|
6006
|
+
description: "Code\u2194spec Level 1: every source file an implementation names \u2014 its own sourcePath and each method's \u2014 must resolve to a real file inside the project root, and every L3 contract method must be realized in its own source file (the method's sourcePath, else the implementation's) at its conformance tier (declared | anchored | off; Portals default to anchored, everything else to declared; per-method `symbol` maps intent-language names to code names), and every finding code a contract method declares must be anchored in that file \u2014 as a string literal or, at exact grade, a property-access name (UNREALIZED_FINDING). A file that escapes the root, is missing or cannot be read is reported once and blocks only the methods realized in it. Findings carry the analysis grade (exact AST | pattern table | generic scan) so weaker analysis is visible. Implementations under chained subsystems (projectPath) validate standalone in their own project run and are skipped here.",
|
|
5973
6007
|
codes: [
|
|
5974
6008
|
{ code: "MISSING_SOURCE_PATH", defaultSeverity: "warning", summary: "An implementation names no source file at all, or a contract method is left without one \u2014 structural conformance cannot link it to code" },
|
|
5975
6009
|
{ code: "MISSING_SOURCE_FILE", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names does not resolve to a file on disk" },
|
|
5976
6010
|
{ code: "SOURCE_PATH_ESCAPES_ROOT", defaultSeverity: "error", summary: "A source file an implementation or one of its methods names is absolute or escapes the project root (containment refusal)" },
|
|
5977
6011
|
{ code: "UNREALIZED_METHOD", defaultSeverity: "warning", summary: "An L3 contract method has no anchor in its own source file (the method's sourcePath, else the implementation's) at the required conformance tier" },
|
|
5978
|
-
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares
|
|
6012
|
+
{ code: "UNREALIZED_FINDING", defaultSeverity: "warning", summary: "A finding code a contract method declares appears in the method's source file neither as a string literal nor as a property-access name (below exact grade, any identifier counts)" },
|
|
5979
6013
|
{ code: "CONFORMANCE_ANALYSIS_SKIPPED", defaultSeverity: "warning", summary: "A source file an implementation or one of its methods names could not be analyzed (binary/unreadable) \u2014 realization of the methods in it was not checked" },
|
|
5980
6014
|
{ code: "CONFORMANCE_DEGRADED", defaultSeverity: "warning", summary: "TypeScript/JavaScript files were analyzed below exact grade (compiler not resolvable) \u2014 dependency conformance skips them" }
|
|
5981
6015
|
],
|
|
@@ -5985,7 +6019,7 @@ var init_structural_conformance = __esm({
|
|
|
5985
6019
|
lookups.set(pathKey(facts.path), {
|
|
5986
6020
|
declared: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames]),
|
|
5987
6021
|
anchored: /* @__PURE__ */ new Set([...facts.declaredNames, ...facts.exportedNames, ...facts.anchoredNames]),
|
|
5988
|
-
|
|
6022
|
+
findingAnchors: new Set(facts.anchoredNames),
|
|
5989
6023
|
facts
|
|
5990
6024
|
});
|
|
5991
6025
|
}
|
|
@@ -6105,11 +6139,11 @@ var init_structural_conformance = __esm({
|
|
|
6105
6139
|
if (!checked) continue;
|
|
6106
6140
|
const { file, lookup } = checked;
|
|
6107
6141
|
for (const finding of method2.findings) {
|
|
6108
|
-
if (lookup.
|
|
6142
|
+
if (lookup.findingAnchors.has(finding.code)) continue;
|
|
6109
6143
|
ctx.addIssue(
|
|
6110
6144
|
"warning",
|
|
6111
6145
|
"UNREALIZED_FINDING",
|
|
6112
|
-
`Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
|
|
6146
|
+
`Method "${method2.name}" of contract "${impl.contract}" declares finding "${finding.code}", but "${file}" has no string literal or property-access name "${finding.code}" (analysis grade: ${lookup.facts.analysisGrade}) \u2014 report the finding under its declared code, or remove the declaration.`,
|
|
6113
6147
|
impl.id,
|
|
6114
6148
|
draft
|
|
6115
6149
|
);
|
|
@@ -6225,12 +6259,18 @@ var init_hidden_state = __esm({
|
|
|
6225
6259
|
check(ctx) {
|
|
6226
6260
|
const byPath = /* @__PURE__ */ new Map();
|
|
6227
6261
|
for (const impl of ctx.implementations) {
|
|
6228
|
-
if (impl.conformance === "off") continue;
|
|
6229
6262
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6230
6263
|
const component = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6231
6264
|
if (!component) continue;
|
|
6232
6265
|
if (ctx.isInChainedSubproject(component.subsystem)) continue;
|
|
6233
|
-
|
|
6266
|
+
const trusted = [];
|
|
6267
|
+
if (impl.sourcePath && impl.conformance !== "off") trusted.push(impl.sourcePath);
|
|
6268
|
+
for (const method2 of impl.methods) {
|
|
6269
|
+
if ((method2.conformance ?? impl.conformance) === "off") continue;
|
|
6270
|
+
const file = methodSourceFile(method2, impl.sourcePath);
|
|
6271
|
+
if (file) trusted.push(file);
|
|
6272
|
+
}
|
|
6273
|
+
for (const file of trusted) {
|
|
6234
6274
|
const key = pathKey(file);
|
|
6235
6275
|
const list2 = byPath.get(key) ?? [];
|
|
6236
6276
|
if (list2.some((m) => m.impl === impl)) continue;
|
|
@@ -6381,7 +6421,7 @@ var init_dependency_conformance = __esm({
|
|
|
6381
6421
|
const fromFiles = filesByComponent.get(component.id);
|
|
6382
6422
|
if (!fromFiles) continue;
|
|
6383
6423
|
const isMountingDeclarer = component.componentType === "Portal" || component.componentType === "Observer";
|
|
6384
|
-
const declaredTargets = [...component.dependsOn, ...component.owns ?? []];
|
|
6424
|
+
const declaredTargets = [.../* @__PURE__ */ new Set([...component.dependsOn, ...component.owns ?? []])];
|
|
6385
6425
|
for (const targetId of declaredTargets) {
|
|
6386
6426
|
const target = ctx.componentMap.get(targetId);
|
|
6387
6427
|
if (!target) continue;
|
|
@@ -6402,11 +6442,14 @@ var init_dependency_conformance = __esm({
|
|
|
6402
6442
|
}
|
|
6403
6443
|
const impls = implsByComponent.get(component.id) ?? [];
|
|
6404
6444
|
const anchor = impls[0];
|
|
6445
|
+
const declaredDependsOn = component.dependsOn.includes(targetId);
|
|
6446
|
+
const declaredOwns = (component.owns ?? []).includes(targetId);
|
|
6447
|
+
const relation = declaredDependsOn && declaredOwns ? "dependsOn and owns" : declaredDependsOn ? "dependsOn" : "owns";
|
|
6405
6448
|
const draft = ctx.isComponentDraft(component.id) || ctx.isComponentDraft(targetId) || impls.some((i) => ctx.isImplementationDraft(i));
|
|
6406
6449
|
ctx.addIssue(
|
|
6407
6450
|
"warning",
|
|
6408
6451
|
"UNREALIZED_DEPENDENCY",
|
|
6409
|
-
`Component "${component.id}" declares ${
|
|
6452
|
+
`Component "${component.id}" declares ${relation} "${targetId}", but no runtime import connects their source files (${[...fromFiles].join(", ")} \u219B ${target.subsystem !== component.subsystem ? `subsystem ${target.subsystem}` : [...filesByComponent.get(targetId) ?? []].join(", ")}) \u2014 either the collaboration is wired indirectly (DI) or the declared edge is stale.`,
|
|
6410
6453
|
anchor?.id ?? component.id,
|
|
6411
6454
|
draft
|
|
6412
6455
|
);
|
|
@@ -6438,31 +6481,31 @@ var init_integration_conformance = __esm({
|
|
|
6438
6481
|
const filesByComponent = /* @__PURE__ */ new Map();
|
|
6439
6482
|
const filesBySubsystem = /* @__PURE__ */ new Map();
|
|
6440
6483
|
const implsByComponent = /* @__PURE__ */ new Map();
|
|
6484
|
+
const adopted = /* @__PURE__ */ new Set();
|
|
6485
|
+
const exists = (p) => {
|
|
6486
|
+
const status2 = factsByPath.get(p)?.status;
|
|
6487
|
+
return status2 === "analyzed" || status2 === "unreadable";
|
|
6488
|
+
};
|
|
6441
6489
|
for (const impl of ctx.implementations) {
|
|
6442
6490
|
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6443
6491
|
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6444
|
-
if (!comp) continue;
|
|
6492
|
+
if (!comp || ctx.isInChainedSubproject(comp.subsystem)) continue;
|
|
6445
6493
|
if (!implsByComponent.has(comp.id)) implsByComponent.set(comp.id, []);
|
|
6446
6494
|
implsByComponent.get(comp.id).push(impl);
|
|
6447
6495
|
for (const file of implementationSourceFiles(impl)) {
|
|
6448
6496
|
const p = pathKey(file);
|
|
6497
|
+
if (!exists(p)) continue;
|
|
6449
6498
|
if (!filesByComponent.has(comp.id)) filesByComponent.set(comp.id, /* @__PURE__ */ new Set());
|
|
6450
6499
|
filesByComponent.get(comp.id).add(p);
|
|
6451
6500
|
if (!filesBySubsystem.has(comp.subsystem)) filesBySubsystem.set(comp.subsystem, /* @__PURE__ */ new Set());
|
|
6452
6501
|
filesBySubsystem.get(comp.subsystem).add(p);
|
|
6453
6502
|
}
|
|
6503
|
+
if (impl.simPath) adopted.add(comp.subsystem);
|
|
6454
6504
|
}
|
|
6455
6505
|
const isTechBoundary = (compId) => (implsByComponent.get(compId) ?? []).some((i) => (i.technologies ?? []).length > 0);
|
|
6456
|
-
const
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
const contract = ctx.interfaceMap.get(impl.contract);
|
|
6460
|
-
const comp = contract ? ctx.componentMap.get(contract.component) : void 0;
|
|
6461
|
-
if (comp) adopted.add(comp.subsystem);
|
|
6462
|
-
}
|
|
6463
|
-
const reachFrom = (start) => {
|
|
6464
|
-
const seen = /* @__PURE__ */ new Set([start]);
|
|
6465
|
-
const stack = [start];
|
|
6506
|
+
const reachFrom = (start2) => {
|
|
6507
|
+
const seen = /* @__PURE__ */ new Set([start2]);
|
|
6508
|
+
const stack = [start2];
|
|
6466
6509
|
while (stack.length) {
|
|
6467
6510
|
const from = stack.pop();
|
|
6468
6511
|
const facts = factsByPath.get(from);
|
|
@@ -6656,8 +6699,8 @@ var init_coupling_health = __esm({
|
|
|
6656
6699
|
);
|
|
6657
6700
|
}
|
|
6658
6701
|
}
|
|
6659
|
-
const threshold = ctx.rules?.complexity?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6660
6702
|
for (const comp of ctx.components) {
|
|
6703
|
+
const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
|
|
6661
6704
|
if (comp.dependsOn.length > threshold) {
|
|
6662
6705
|
ctx.addIssue(
|
|
6663
6706
|
"warning",
|
|
@@ -6785,7 +6828,7 @@ var init_target_language = __esm({
|
|
|
6785
6828
|
if (!lang) continue;
|
|
6786
6829
|
const gaps = gapsFor(lang);
|
|
6787
6830
|
if (Object.keys(gaps).length === 0) continue;
|
|
6788
|
-
const isDraftCtx =
|
|
6831
|
+
const isDraftCtx = ctx.isImplementationDraft(impl);
|
|
6789
6832
|
for (const implMethod of impl.methods) {
|
|
6790
6833
|
for (const step of implMethod.narrative) {
|
|
6791
6834
|
const constructs = [
|
|
@@ -6887,7 +6930,7 @@ var init_technology_boundaries = __esm({
|
|
|
6887
6930
|
"TECH_ON_LOGIC_COMPONENT",
|
|
6888
6931
|
`Implementation "${impl.id}" binds technology (${impl.technologies.join(", ")}) on component "${comp.id}" (${comp.componentType}). Technology belongs behind a data-layer seam \u2014 extract an Adapter (or Store/Registry/Index) behind an intent interface and let this component depend on that.`,
|
|
6889
6932
|
impl.id,
|
|
6890
|
-
ctx.
|
|
6933
|
+
ctx.isImplementationDraft(impl)
|
|
6891
6934
|
);
|
|
6892
6935
|
}
|
|
6893
6936
|
const compSet = ownsClosure(ownershipRoot(comp.id));
|
|
@@ -6925,7 +6968,7 @@ var init_technology_boundaries = __esm({
|
|
|
6925
6968
|
"VENDOR_NAME_IN_CONTRACT",
|
|
6926
6969
|
`Interface "${intf.id}" exposes technology "${home.label}" in contract identifiers (method${offending.length > 1 ? "s" : ""}: ${offending.join(", ")}). Name the intent, not the vendor \u2014 the technology is owned by component ${ownersDesc(home)} and must stay swappable behind this contract.`,
|
|
6927
6970
|
intf.id,
|
|
6928
|
-
ctx.isComponentDraft(intf.component)
|
|
6971
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6929
6972
|
);
|
|
6930
6973
|
}
|
|
6931
6974
|
}
|
|
@@ -6956,7 +6999,8 @@ var init_technology_boundaries = __esm({
|
|
|
6956
6999
|
"warning",
|
|
6957
7000
|
"TECH_LEAKAGE",
|
|
6958
7001
|
`Subsystem "${sub.id}" references "${home.label}" outside the technology's owning boundary (component ${ownersDesc(home)}).`,
|
|
6959
|
-
sub.id
|
|
7002
|
+
sub.id,
|
|
7003
|
+
isDraftSubsystem(sub)
|
|
6960
7004
|
);
|
|
6961
7005
|
}
|
|
6962
7006
|
}
|
|
@@ -6969,7 +7013,7 @@ var init_technology_boundaries = __esm({
|
|
|
6969
7013
|
"TECH_LEAKAGE",
|
|
6970
7014
|
`Interface "${intf.id}" describes "${home.label}" outside its owning boundary (component ${ownersDesc(home)}) \u2014 consumers must not know backend specifics.`,
|
|
6971
7015
|
intf.id,
|
|
6972
|
-
ctx.isComponentDraft(intf.component)
|
|
7016
|
+
ctx.isComponentDraft(intf.component) || intf.status === "draft" || intf.status === "design"
|
|
6973
7017
|
);
|
|
6974
7018
|
}
|
|
6975
7019
|
}
|
|
@@ -6989,7 +7033,8 @@ var init_technology_boundaries = __esm({
|
|
|
6989
7033
|
"warning",
|
|
6990
7034
|
"TECH_LEAKAGE",
|
|
6991
7035
|
`Implementation "${impl.id}" references "${home.label}" outside its owning boundary \u2014 call the intent interface of component ${ownersDesc(home)} instead of naming its technology.`,
|
|
6992
|
-
impl.id
|
|
7036
|
+
impl.id,
|
|
7037
|
+
ctx.isImplementationDraft(impl)
|
|
6993
7038
|
);
|
|
6994
7039
|
}
|
|
6995
7040
|
}
|
|
@@ -7052,6 +7097,7 @@ var casingPatterns, isAllUppercase, namingRule;
|
|
|
7052
7097
|
var init_naming_conventions = __esm({
|
|
7053
7098
|
"src/core/rules/heuristic/naming-conventions.ts"() {
|
|
7054
7099
|
"use strict";
|
|
7100
|
+
init_models();
|
|
7055
7101
|
casingPatterns = {
|
|
7056
7102
|
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
7057
7103
|
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
@@ -7072,9 +7118,10 @@ var init_naming_conventions = __esm({
|
|
|
7072
7118
|
for (const sub of ctx.subsystems) {
|
|
7073
7119
|
const namingConfig = ctx.namingConfigFor(sub.id);
|
|
7074
7120
|
if (!namingConfig?.subsystems) continue;
|
|
7121
|
+
const isDraft = isDraftSubsystem(sub);
|
|
7075
7122
|
const baseId = getBaseId(sub.id);
|
|
7076
|
-
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
|
|
7077
|
-
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id);
|
|
7123
|
+
checkNamedValue(ctx, baseId, namingConfig.subsystems, `Subsystem ID "${sub.id}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7124
|
+
checkNamedValue(ctx, sub.name, namingConfig.subsystems, `Subsystem Name "${sub.name}" does not match naming convention "${namingConfig.subsystems}".`, sub.id, isDraft);
|
|
7078
7125
|
}
|
|
7079
7126
|
for (const comp of ctx.components) {
|
|
7080
7127
|
const namingConfig = ctx.namingConfigFor(comp.subsystem);
|
|
@@ -7159,7 +7206,7 @@ var init_naming_conventions = __esm({
|
|
|
7159
7206
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7160
7207
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7161
7208
|
const namingConfig = ctx.namingConfigFor(comp?.subsystem);
|
|
7162
|
-
const isDraft =
|
|
7209
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7163
7210
|
if (namingConfig?.methods) {
|
|
7164
7211
|
for (const m of impl.methods) {
|
|
7165
7212
|
checkNamedValue(ctx, m.name, namingConfig.methods, `Implementation method "${m.name}" on "${impl.id}" does not match naming convention "${namingConfig.methods}".`, impl.id, isDraft);
|
|
@@ -7223,6 +7270,7 @@ var complexityRule;
|
|
|
7223
7270
|
var init_complexity_and_metadata = __esm({
|
|
7224
7271
|
"src/core/rules/heuristic/complexity-and-metadata.ts"() {
|
|
7225
7272
|
"use strict";
|
|
7273
|
+
init_models();
|
|
7226
7274
|
complexityRule = {
|
|
7227
7275
|
name: "complexity-and-metadata",
|
|
7228
7276
|
description: "Enforces metadata documentation completeness (checking if descriptions are missing or too short) and structural complexity caps (limits on methods per interface, dependencies per component, and narrative steps per method) configured globally or in architectural profiles.",
|
|
@@ -7238,7 +7286,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7238
7286
|
check(ctx) {
|
|
7239
7287
|
for (const sub of ctx.subsystems) {
|
|
7240
7288
|
const docConfig = ctx.documentationConfigFor(sub.id);
|
|
7241
|
-
|
|
7289
|
+
const isSubDraft = isDraftSubsystem(sub);
|
|
7290
|
+
checkDescription(ctx, sub.description, docConfig?.requireDescriptions ?? false, docConfig?.minDescriptionLength, sub.id, "Subsystem", isSubDraft);
|
|
7242
7291
|
const complexityConfig = ctx.complexityConfigFor(sub.id);
|
|
7243
7292
|
const directComponents = ctx.components.filter((c) => c.subsystem === sub.id).length;
|
|
7244
7293
|
if (complexityConfig?.maxSubsystemComponents !== void 0 && directComponents > complexityConfig.maxSubsystemComponents) {
|
|
@@ -7246,7 +7295,8 @@ var init_complexity_and_metadata = __esm({
|
|
|
7246
7295
|
"warning",
|
|
7247
7296
|
"EXCESSIVE_SUBSYSTEM_COMPONENTS",
|
|
7248
7297
|
`Subsystem "${sub.id}" has ${directComponents} direct components, exceeding the configured limit of ${complexityConfig.maxSubsystemComponents}.`,
|
|
7249
|
-
sub.id
|
|
7298
|
+
sub.id,
|
|
7299
|
+
isSubDraft
|
|
7250
7300
|
);
|
|
7251
7301
|
}
|
|
7252
7302
|
}
|
|
@@ -7322,7 +7372,7 @@ var init_complexity_and_metadata = __esm({
|
|
|
7322
7372
|
const intf = ctx.interfaceMap.get(impl.contract);
|
|
7323
7373
|
const comp = intf ? ctx.componentMap.get(intf.component) : void 0;
|
|
7324
7374
|
const complexityConfig = ctx.complexityConfigFor(comp?.subsystem);
|
|
7325
|
-
const isDraft =
|
|
7375
|
+
const isDraft = ctx.isImplementationDraft(impl);
|
|
7326
7376
|
if (complexityConfig?.maxNarrativeSteps !== void 0) {
|
|
7327
7377
|
for (const m of impl.methods) {
|
|
7328
7378
|
if (m.narrative.length > complexityConfig.maxNarrativeSteps) {
|
|
@@ -8838,14 +8888,14 @@ function buildExcalidrawScene(model, L) {
|
|
|
8838
8888
|
const src = rectById[edge.from], tgt = rectById[edge.to];
|
|
8839
8889
|
if (!a || !b || !src || !tgt) return;
|
|
8840
8890
|
const leftToRight = b.x >= a.x + a.w;
|
|
8841
|
-
const
|
|
8891
|
+
const start2 = leftToRight ? { x: a.x + a.w, y: a.y + a.h / 2 } : { x: a.x, y: a.y + a.h / 2 };
|
|
8842
8892
|
const end = leftToRight ? { x: b.x, y: b.y + b.h / 2 } : { x: b.x + b.w, y: b.y + b.h / 2 };
|
|
8843
8893
|
const id = "edge-" + edgeN++;
|
|
8844
|
-
const arrow = base(id, "arrow", { x:
|
|
8894
|
+
const arrow = base(id, "arrow", { x: start2.x, y: start2.y, w: Math.abs(end.x - start2.x), h: Math.abs(end.y - start2.y) });
|
|
8845
8895
|
arrow.roundness = { type: 2 };
|
|
8846
8896
|
arrow.strokeColor = edge.cross ? "#c26767" : "#8d97a5";
|
|
8847
8897
|
arrow.strokeWidth = edge.cross ? 2 : 1;
|
|
8848
|
-
arrow.points = [[0, 0], [end.x -
|
|
8898
|
+
arrow.points = [[0, 0], [end.x - start2.x, end.y - start2.y]];
|
|
8849
8899
|
arrow.lastCommittedPoint = null;
|
|
8850
8900
|
arrow.startBinding = { elementId: src.id, focus: 0, gap: 4 };
|
|
8851
8901
|
arrow.endBinding = { elementId: tgt.id, focus: 0, gap: 4 };
|
|
@@ -14029,7 +14079,7 @@ function buildRuleContext(opts) {
|
|
|
14029
14079
|
return rules.sddRuleSeverity[ruleCode];
|
|
14030
14080
|
}
|
|
14031
14081
|
const sub = subsystemId ? subsystems.find((s) => s.id === subsystemId) : void 0;
|
|
14032
|
-
const profileSeverity = extensions.profiles[sub?.profile
|
|
14082
|
+
const profileSeverity = extensions.profiles[sub?.profile || projectType]?.rules?.sddRuleSeverity?.[ruleCode];
|
|
14033
14083
|
if (profileSeverity) {
|
|
14034
14084
|
return profileSeverity;
|
|
14035
14085
|
}
|
|
@@ -14516,28 +14566,52 @@ var init_domains = __esm({
|
|
|
14516
14566
|
function globalVariantsDir() {
|
|
14517
14567
|
return process.env.WAIRON_VARIANTS_DIR ?? path16.join(os5.homedir(), ".wairon", "variants");
|
|
14518
14568
|
}
|
|
14519
|
-
function
|
|
14569
|
+
function builtinVariantsDir() {
|
|
14570
|
+
const candidates = [
|
|
14571
|
+
path16.resolve(__dirname, "..", "templates", "variants"),
|
|
14572
|
+
// src/core & dist/cli
|
|
14573
|
+
path16.resolve(__dirname, "templates", "variants")
|
|
14574
|
+
// dist (library entry)
|
|
14575
|
+
];
|
|
14576
|
+
return candidates.find((dir) => fs12.existsSync(dir)) ?? candidates[0];
|
|
14577
|
+
}
|
|
14578
|
+
function projectVariantsDir() {
|
|
14579
|
+
return path16.join(getProjectRoot(), ".wai", "variants");
|
|
14580
|
+
}
|
|
14581
|
+
function variantFiles(dir) {
|
|
14520
14582
|
let entries;
|
|
14521
14583
|
try {
|
|
14522
14584
|
entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
14523
14585
|
} catch {
|
|
14524
14586
|
return [];
|
|
14525
14587
|
}
|
|
14588
|
+
return entries.filter((e) => e.isFile() && /\.ya?ml$/i.test(e.name)).sort((a, b) => a.name.localeCompare(b.name)).map((e) => path16.join(dir, e.name));
|
|
14589
|
+
}
|
|
14590
|
+
function parseVariants(raw) {
|
|
14591
|
+
if (raw == null) return [];
|
|
14592
|
+
return (Array.isArray(raw) ? raw : [raw]).map((item) => VariantDefSchema.parse(item));
|
|
14593
|
+
}
|
|
14594
|
+
function reportSkippedFile(file, err) {
|
|
14595
|
+
console.error(`[variants] skipped "${file}": ${err instanceof Error ? err.message : String(err)}`);
|
|
14596
|
+
}
|
|
14597
|
+
function readVariantsDir(dir) {
|
|
14526
14598
|
const out = [];
|
|
14527
|
-
for (const
|
|
14528
|
-
if (!e.isFile() || !/\.ya?ml$/i.test(e.name)) continue;
|
|
14529
|
-
const full = path16.join(dir, e.name);
|
|
14599
|
+
for (const file of variantFiles(dir)) {
|
|
14530
14600
|
try {
|
|
14531
|
-
|
|
14532
|
-
if (raw == null) continue;
|
|
14533
|
-
const items = Array.isArray(raw) ? raw : [raw];
|
|
14534
|
-
for (const item of items) out.push(VariantDefSchema.parse(item));
|
|
14601
|
+
out.push(...parseVariants(readYamlFile(file)));
|
|
14535
14602
|
} catch (err) {
|
|
14536
|
-
|
|
14603
|
+
reportSkippedFile(file, err);
|
|
14537
14604
|
}
|
|
14538
14605
|
}
|
|
14539
14606
|
return out;
|
|
14540
14607
|
}
|
|
14608
|
+
function mergeById(...layers) {
|
|
14609
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14610
|
+
for (const layer of layers) {
|
|
14611
|
+
for (const v of layer) byId.set(v.id, v);
|
|
14612
|
+
}
|
|
14613
|
+
return [...byId.values()];
|
|
14614
|
+
}
|
|
14541
14615
|
function resolveVariantGuidance(component, allComponents, variantsById) {
|
|
14542
14616
|
if (!component.variant) return null;
|
|
14543
14617
|
const def = variantsById.get(component.variant);
|
|
@@ -14571,10 +14645,11 @@ function composeVariantGuidance(comps, allComponents, variantsById) {
|
|
|
14571
14645
|
}
|
|
14572
14646
|
function loadProjectVariants() {
|
|
14573
14647
|
try {
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14648
|
+
return mergeById(
|
|
14649
|
+
readVariantsDir(builtinVariantsDir()),
|
|
14650
|
+
readVariantsDir(globalVariantsDir()),
|
|
14651
|
+
readVariantsDir(projectVariantsDir())
|
|
14652
|
+
);
|
|
14578
14653
|
} catch {
|
|
14579
14654
|
return [];
|
|
14580
14655
|
}
|
|
@@ -14592,7 +14667,7 @@ var init_variants = __esm({
|
|
|
14592
14667
|
VariantDefSchema = import_zod10.z.object({
|
|
14593
14668
|
/** Variant id referenced by a component's `variant` (e.g. "publisher", "org/external-config-adapter"). */
|
|
14594
14669
|
id: import_zod10.z.string().min(1),
|
|
14595
|
-
/** The core stereotype this variant specializes — authoritative for generic semantics (Adapter,
|
|
14670
|
+
/** The core stereotype this variant specializes — authoritative for generic semantics (Adapter, Orchestrator, …). */
|
|
14596
14671
|
base: import_zod10.z.string().min(1),
|
|
14597
14672
|
/** How to implement a component of this variant — the recipe the implementer follows and reuses across same-variant components. */
|
|
14598
14673
|
guidance: import_zod10.z.string().min(1),
|
|
@@ -16377,10 +16452,10 @@ var require_node = __commonJS({
|
|
|
16377
16452
|
0
|
|
16378
16453
|
]);
|
|
16379
16454
|
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
16380
|
-
var freb = function(eb,
|
|
16455
|
+
var freb = function(eb, start2) {
|
|
16381
16456
|
var b = new u16(31);
|
|
16382
16457
|
for (var i2 = 0; i2 < 31; ++i2) {
|
|
16383
|
-
b[i2] =
|
|
16458
|
+
b[i2] = start2 += 1 << eb[i2 - 1];
|
|
16384
16459
|
}
|
|
16385
16460
|
var r = new i32(b[30]);
|
|
16386
16461
|
for (var i2 = 1; i2 < 30; ++i2) {
|
|
@@ -24775,7 +24850,7 @@ NOTICE:
|
|
|
24775
24850
|
name: import_zod11.z.string().describe("Human-readable display name"),
|
|
24776
24851
|
description: import_zod11.z.string().describe("Responsibility / internal architecture details"),
|
|
24777
24852
|
subsystem: import_zod11.z.string().describe("The L1 subsystem ID this component belongs to"),
|
|
24778
|
-
componentType: import_zod11.z.enum(["Portal", "Orchestrator", "Supervisor", "Actor", "Store", "Index", "
|
|
24853
|
+
componentType: import_zod11.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"),
|
|
24779
24854
|
owns: import_zod11.z.array(import_zod11.z.string()).optional().describe("Member block ids privately owned by this component (patterns only)"),
|
|
24780
24855
|
dependsOn: import_zod11.z.array(import_zod11.z.string()).optional().describe("IDs of other components this collaborates with (facades or standalone blocks)"),
|
|
24781
24856
|
portalType: import_zod11.z.enum(["HTTP_API", "gRPC", "GraphQL", "MessageBus", "CLI", "NamedPipe", "IPC", "Custom"]).optional().describe("Portal-only, and expected on every Portal. OMIT IT on any other componentType \u2014 passing it there is refused at the write (UNEXPECTED_PORTAL_FIELD), nothing is saved."),
|
|
@@ -24787,6 +24862,7 @@ NOTICE:
|
|
|
24787
24862
|
description: import_zod11.z.string().optional()
|
|
24788
24863
|
})).optional().describe("Portal-only: capability \u2192 component.method dispatch table for generic-handle portals. Gives the reachability walker real edges and is validated against target interfaces (UNSERVED_CAPABILITY)."),
|
|
24789
24864
|
durability: import_zod11.z.enum(["ram-projection", "durable", "read-through", "cache"]).optional().describe("Store-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DURABILITY_ON_NON_STORE) and nothing is saved. Every Store should declare one (MISSING_DURABILITY): durable = persisted RAM projection (hydration read-back from a lifecycle init entrypoint required \u2014 MISSING_HYDRATION); read-through = persisted with no RAM copy (every read is the read-back, hydration exempt); ram-projection = rebuilt not restored; cache = evictable loss-safe memo state."),
|
|
24865
|
+
dependencyClass: import_zod11.z.enum(["pure", "read"]).optional().describe("Orchestrator-only \u2014 OMIT IT on any other componentType, where it is refused at the write (DEPENDENCY_CLASS_ON_NON_ORCHESTRATOR) and nothing is saved. Declares what logic may depend on, enforced as a Store's durability is (DEPENDENCY_CLASS_VIOLATION): pure = depends only on pure Orchestrators (a computation over the values it is handed, e.g. an arbiter or codec); read = also on read Orchestrators, Repositories, Indexes and Adapters, never calling their write methods; unset = a workflow."),
|
|
24790
24866
|
emits: import_zod11.z.array(import_zod11.z.object({
|
|
24791
24867
|
topic: import_zod11.z.string().describe("Topic/channel name exactly as used on the bus"),
|
|
24792
24868
|
event: import_zod11.z.string().optional().describe("Optional event name within the topic (informational; pairing is by topic)"),
|
|
@@ -24804,10 +24880,10 @@ NOTICE:
|
|
|
24804
24880
|
server,
|
|
24805
24881
|
"sdd_add_component",
|
|
24806
24882
|
{
|
|
24807
|
-
description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer
|
|
24883
|
+
description: `Add an L2 Component under a subsystem. componentType is a building block (Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer) or the pattern Repository. Specialist and Gateway are retired (STEREOTYPE_RETIRED) and cannot be authored: logic is an Orchestrator with a dependencyClass (pure | read; unset = a workflow), and a gateway is a Portal with the gateway variant (set variant with sdd_update_spec). Patterns set "owns" (their private member blocks); all components set "dependsOn" (collaborators \u2014 facades or standalone blocks). Held/persisted state (configs, permissions, sessions, caches): model the Repository recipe \u2014 a Store + Registry (write) + Index (read), plus a Query for computed reads over the Store, owned by a Repository facade consumers depend on; a deliberately standalone Store is the sanctioned lightweight form (workflow-layer consumers + lint.allow on UNOWNED_STORE). Never hold state as fields inside an Orchestrator because a Store link was refused. Re-running it on an existing id RE-AUTHORS it: the fields above are replaced (an omitted array is CLEARED), while lint.allow, a Portal's auth, variant, patterns and externalLinks are carried forward \u2014 edit those with sdd_update_spec.`,
|
|
24808
24884
|
inputSchema: componentInput
|
|
24809
24885
|
},
|
|
24810
|
-
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, emits, subscribesTo, ext }) => {
|
|
24886
|
+
({ id, name, description, subsystem, componentType, owns, dependsOn, portalType, basePath, dispatch, durability, dependencyClass, emits, subscribesTo, ext }) => {
|
|
24811
24887
|
try {
|
|
24812
24888
|
const { loadSubsystemSpec: loadSubsystemSpec2, loadComponentSpec: loadComponentSpec2 } = requireSpecs();
|
|
24813
24889
|
const sub = loadSubsystemSpec2(subsystem);
|
|
@@ -24825,6 +24901,7 @@ NOTICE:
|
|
|
24825
24901
|
...basePath ? { basePath } : {},
|
|
24826
24902
|
...dispatch ? { dispatch } : {},
|
|
24827
24903
|
...durability ? { durability } : {},
|
|
24904
|
+
...dependencyClass ? { dependencyClass } : {},
|
|
24828
24905
|
...emits ? { emits } : {},
|
|
24829
24906
|
...subscribesTo ? { subscribesTo } : {},
|
|
24830
24907
|
...ext ? { ext } : {},
|
|
@@ -24876,7 +24953,7 @@ NOTICE:
|
|
|
24876
24953
|
code: import_zod11.z.string().describe("UPPER_SNAKE finding code, unique within the method; a pack's codes carry the pack prefix (<PACK>_<CODE>)"),
|
|
24877
24954
|
severity: import_zod11.z.enum(["error", "warning"]).describe("Default severity, before project severity overrides and draft-context downgrades"),
|
|
24878
24955
|
summary: import_zod11.z.string().describe("One line saying what the finding means")
|
|
24879
|
-
})).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must
|
|
24956
|
+
})).optional().describe("The finding codes this method can report, each with its default severity and summary. Each declared code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). A code is declared once per method; sdd_update_spec upserts and deletes findings by code."),
|
|
24880
24957
|
ext: import_zod11.z.record(import_zod11.z.unknown()).optional().describe("Opaque pack/tool extension data for this method (namespaced keys) \u2014 preserved verbatim")
|
|
24881
24958
|
};
|
|
24882
24959
|
const interfaceMethodInputFields = Object.keys(interfaceMethodShape);
|
|
@@ -24892,7 +24969,7 @@ NOTICE:
|
|
|
24892
24969
|
server,
|
|
24893
24970
|
"sdd_define_interface",
|
|
24894
24971
|
{
|
|
24895
|
-
description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must
|
|
24972
|
+
description: "Define an L3 Contract / Interface with method signatures for a component. Prefer supplying structured `params` per method \u2014 they are the authoritative source for type checking (the free-form signature string then becomes display-only and is never heuristically parsed). A method declares the finding codes it reports in `findings` ({code, severity, summary}); each code must be anchored in the method's source file, as a string literal or a property-access name (UNREALIZED_FINDING). Re-defining an existing id REPLACES the method list: a method left out of the input is REMOVED (and reported); spec-level lint/ext and each method's endpoint binding are carried forward.",
|
|
24896
24973
|
inputSchema: interfaceInput
|
|
24897
24974
|
},
|
|
24898
24975
|
({ id, name, description, component, methods }) => {
|
|
@@ -27526,14 +27603,14 @@ var http = __toESM(require("http"));
|
|
|
27526
27603
|
var https = __toESM(require("https"));
|
|
27527
27604
|
init_defaults();
|
|
27528
27605
|
function downloadFile(url, dest) {
|
|
27529
|
-
return new Promise((
|
|
27606
|
+
return new Promise((resolve32, reject) => {
|
|
27530
27607
|
const file = fs23.createWriteStream(dest);
|
|
27531
27608
|
const get4 = url.startsWith("https://") ? https.get : http.get;
|
|
27532
27609
|
get4(url, { headers: { "User-Agent": `wairon/${WAIRON_VERSION}` }, agent: false }, (res) => {
|
|
27533
27610
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
27534
27611
|
file.close();
|
|
27535
27612
|
res.destroy();
|
|
27536
|
-
downloadFile(res.headers.location, dest).then(
|
|
27613
|
+
downloadFile(res.headers.location, dest).then(resolve32).catch(reject);
|
|
27537
27614
|
return;
|
|
27538
27615
|
}
|
|
27539
27616
|
if (res.statusCode !== 200) {
|
|
@@ -27545,7 +27622,7 @@ function downloadFile(url, dest) {
|
|
|
27545
27622
|
res.pipe(file);
|
|
27546
27623
|
file.on("finish", () => {
|
|
27547
27624
|
res.destroy();
|
|
27548
|
-
file.close(() =>
|
|
27625
|
+
file.close(() => resolve32());
|
|
27549
27626
|
});
|
|
27550
27627
|
file.on("error", (err) => {
|
|
27551
27628
|
res.destroy();
|
|
@@ -27722,7 +27799,7 @@ function releaseChannelLabel(tag) {
|
|
|
27722
27799
|
return version.slice(dash + 1).replace(/\.\d+$/, "") || "stable";
|
|
27723
27800
|
}
|
|
27724
27801
|
function fetchReleases(repo) {
|
|
27725
|
-
return new Promise((
|
|
27802
|
+
return new Promise((resolve32, reject) => {
|
|
27726
27803
|
const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
|
|
27727
27804
|
const options = {
|
|
27728
27805
|
headers: {
|
|
@@ -27742,7 +27819,7 @@ function fetchReleases(repo) {
|
|
|
27742
27819
|
return;
|
|
27743
27820
|
}
|
|
27744
27821
|
try {
|
|
27745
|
-
|
|
27822
|
+
resolve32(JSON.parse(data));
|
|
27746
27823
|
} catch {
|
|
27747
27824
|
reject(new Error("Failed to parse GitHub API response"));
|
|
27748
27825
|
}
|
|
@@ -27917,7 +27994,7 @@ async function filteredCheckbox(config) {
|
|
|
27917
27994
|
32,
|
|
27918
27995
|
Math.max(...items.map((i) => i.label.length))
|
|
27919
27996
|
);
|
|
27920
|
-
return new Promise((
|
|
27997
|
+
return new Promise((resolve32) => {
|
|
27921
27998
|
const checked = /* @__PURE__ */ new Set();
|
|
27922
27999
|
let cursor = 0;
|
|
27923
28000
|
let filterIdx = 0;
|
|
@@ -27984,7 +28061,7 @@ async function filteredCheckbox(config) {
|
|
|
27984
28061
|
);
|
|
27985
28062
|
teardown();
|
|
27986
28063
|
const result = items.filter((_, idx) => checked.has(idx)).map((i) => i.value);
|
|
27987
|
-
|
|
28064
|
+
resolve32(result);
|
|
27988
28065
|
}
|
|
27989
28066
|
function abort() {
|
|
27990
28067
|
process.stdout.write("\n");
|
|
@@ -36141,7 +36218,7 @@ async function awaitApproval(cfg, credential, requestId, timeoutSeconds) {
|
|
|
36141
36218
|
let current2 = req;
|
|
36142
36219
|
while (current2.status === "pending" && Date.now() < deadline) {
|
|
36143
36220
|
const remaining = deadline - Date.now();
|
|
36144
|
-
await new Promise((
|
|
36221
|
+
await new Promise((resolve32) => setTimeout(resolve32, Math.min(AWAIT_POLL_INTERVAL_MS, remaining)));
|
|
36145
36222
|
expirePendingApprovals(cfg.dataDir, (/* @__PURE__ */ new Date()).toISOString());
|
|
36146
36223
|
current2 = getApprovalRequestById(cfg.dataDir, requestId) ?? current2;
|
|
36147
36224
|
}
|
|
@@ -41391,6 +41468,52 @@ function serveSharedDownload(cfg, token, kind, req, res) {
|
|
|
41391
41468
|
res.end(result.content);
|
|
41392
41469
|
}
|
|
41393
41470
|
|
|
41471
|
+
// src/server/backup-schedule.ts
|
|
41472
|
+
var TICK_MS = 6e4;
|
|
41473
|
+
var timer;
|
|
41474
|
+
function sweep(config) {
|
|
41475
|
+
try {
|
|
41476
|
+
runPeriodicGitSync(config);
|
|
41477
|
+
} catch (err) {
|
|
41478
|
+
console.error("[git-sync] project sweep failed: " + (err instanceof Error ? err.message : String(err)));
|
|
41479
|
+
}
|
|
41480
|
+
try {
|
|
41481
|
+
runPeriodicBackingSync(config);
|
|
41482
|
+
} catch (err) {
|
|
41483
|
+
console.error("[git-backing] backing sweep failed: " + (err instanceof Error ? err.message : String(err)));
|
|
41484
|
+
}
|
|
41485
|
+
}
|
|
41486
|
+
function start(config) {
|
|
41487
|
+
if (timer) clearInterval(timer);
|
|
41488
|
+
timer = setInterval(() => sweep(config), TICK_MS);
|
|
41489
|
+
timer.unref();
|
|
41490
|
+
}
|
|
41491
|
+
function stop() {
|
|
41492
|
+
if (timer) {
|
|
41493
|
+
clearInterval(timer);
|
|
41494
|
+
timer = void 0;
|
|
41495
|
+
}
|
|
41496
|
+
}
|
|
41497
|
+
|
|
41498
|
+
// src/server/instance-bootstrap.ts
|
|
41499
|
+
var DEV_UNIT_ID = "local";
|
|
41500
|
+
function bootstrapInstance(cfg) {
|
|
41501
|
+
ensureInstanceIdentity(cfg.dataDir);
|
|
41502
|
+
if (cfg.devMode && !getOrganizationUnit(cfg.dataDir, DEV_UNIT_ID)) {
|
|
41503
|
+
const system = { userId: "system", kind: "service", issuer: "local" };
|
|
41504
|
+
createUnit(cfg.dataDir, {
|
|
41505
|
+
id: DEV_UNIT_ID,
|
|
41506
|
+
name: "Local Development",
|
|
41507
|
+
kind: "team",
|
|
41508
|
+
slug: DEV_UNIT_ID,
|
|
41509
|
+
// a root unit's qualified id IS its slug
|
|
41510
|
+
status: "active",
|
|
41511
|
+
createdAt: "",
|
|
41512
|
+
createdBy: system
|
|
41513
|
+
});
|
|
41514
|
+
}
|
|
41515
|
+
}
|
|
41516
|
+
|
|
41394
41517
|
// src/server/http.ts
|
|
41395
41518
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
41396
41519
|
var PayloadTooLargeError = class extends Error {
|
|
@@ -41815,23 +41938,6 @@ async function routeAdmin(cfg, req, res) {
|
|
|
41815
41938
|
sendJson(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
41816
41939
|
}
|
|
41817
41940
|
}
|
|
41818
|
-
var DEV_UNIT_ID = "local";
|
|
41819
|
-
function initHostInstance(cfg) {
|
|
41820
|
-
ensureInstanceIdentity(cfg.dataDir);
|
|
41821
|
-
if (cfg.devMode && !getOrganizationUnit(cfg.dataDir, DEV_UNIT_ID)) {
|
|
41822
|
-
const system = { userId: "system", kind: "service", issuer: "local" };
|
|
41823
|
-
createUnit(cfg.dataDir, {
|
|
41824
|
-
id: DEV_UNIT_ID,
|
|
41825
|
-
name: "Local Development",
|
|
41826
|
-
kind: "team",
|
|
41827
|
-
slug: DEV_UNIT_ID,
|
|
41828
|
-
// a root unit's qualified id IS its slug
|
|
41829
|
-
status: "active",
|
|
41830
|
-
createdAt: "",
|
|
41831
|
-
createdBy: system
|
|
41832
|
-
});
|
|
41833
|
-
}
|
|
41834
|
-
}
|
|
41835
41941
|
function warnIfDataDirUnmigrated(cfg) {
|
|
41836
41942
|
try {
|
|
41837
41943
|
const report2 = migratePermissionModel(cfg.dataDir, false);
|
|
@@ -41866,7 +41972,7 @@ function startHostServer(cfg) {
|
|
|
41866
41972
|
);
|
|
41867
41973
|
}
|
|
41868
41974
|
}
|
|
41869
|
-
|
|
41975
|
+
bootstrapInstance(cfg);
|
|
41870
41976
|
warnIfDataDirUnmigrated(cfg);
|
|
41871
41977
|
const dataServer = http2.createServer((req, res) => routeData(cfg, req, res));
|
|
41872
41978
|
const adminServer = http2.createServer((req, res) => {
|
|
@@ -41877,23 +41983,10 @@ function startHostServer(cfg) {
|
|
|
41877
41983
|
});
|
|
41878
41984
|
dataServer.listen(cfg.port, cfg.host);
|
|
41879
41985
|
adminServer.listen(cfg.adminPort, cfg.adminHost);
|
|
41880
|
-
|
|
41881
|
-
const gitSyncTimer = setInterval(() => {
|
|
41882
|
-
try {
|
|
41883
|
-
runPeriodicGitSync(cfg);
|
|
41884
|
-
} catch (err) {
|
|
41885
|
-
console.error("[git-sync] project sweep failed: " + (err instanceof Error ? err.message : String(err)));
|
|
41886
|
-
}
|
|
41887
|
-
try {
|
|
41888
|
-
runPeriodicBackingSync(cfg);
|
|
41889
|
-
} catch (err) {
|
|
41890
|
-
console.error("[git-backing] backing sweep failed: " + (err instanceof Error ? err.message : String(err)));
|
|
41891
|
-
}
|
|
41892
|
-
}, GIT_SYNC_TICK_MS);
|
|
41893
|
-
gitSyncTimer.unref();
|
|
41986
|
+
start(cfg);
|
|
41894
41987
|
return {
|
|
41895
41988
|
close() {
|
|
41896
|
-
|
|
41989
|
+
stop();
|
|
41897
41990
|
getRealtimeHub().closeAll();
|
|
41898
41991
|
dataServer.close();
|
|
41899
41992
|
adminServer.close();
|
|
@@ -42454,11 +42547,11 @@ async function runServe(options = {}) {
|
|
|
42454
42547
|
logger.info(` data dir: ${import_chalk17.default.gray(cfg.dataDir)}`);
|
|
42455
42548
|
logger.blank();
|
|
42456
42549
|
logger.info("Press Ctrl+C to stop.");
|
|
42457
|
-
await new Promise((
|
|
42550
|
+
await new Promise((resolve32) => {
|
|
42458
42551
|
const shutdown = () => {
|
|
42459
42552
|
logger.info("Shutting down\u2026");
|
|
42460
42553
|
handle.close();
|
|
42461
|
-
|
|
42554
|
+
resolve32();
|
|
42462
42555
|
};
|
|
42463
42556
|
process.on("SIGINT", shutdown);
|
|
42464
42557
|
process.on("SIGTERM", shutdown);
|
|
@@ -42523,11 +42616,11 @@ async function runDev(options = {}) {
|
|
|
42523
42616
|
logger.blank();
|
|
42524
42617
|
logger.info("An agent edits specs; refresh the page to see the live graph. Press Ctrl+C to stop.");
|
|
42525
42618
|
if (options.open) openBrowser(url);
|
|
42526
|
-
await new Promise((
|
|
42619
|
+
await new Promise((resolve32) => {
|
|
42527
42620
|
const shutdown = () => {
|
|
42528
42621
|
logger.info("Shutting down\u2026");
|
|
42529
42622
|
handle.close();
|
|
42530
|
-
|
|
42623
|
+
resolve32();
|
|
42531
42624
|
};
|
|
42532
42625
|
process.on("SIGINT", shutdown);
|
|
42533
42626
|
process.on("SIGTERM", shutdown);
|