flowdular 0.2.3 → 0.3.0

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.
Files changed (3) hide show
  1. package/dist/distribution.js +593 -137
  2. package/dist/index.js +7457 -5524
  3. package/package.json +2 -2
@@ -534,7 +534,7 @@ var require_inc = __commonJS({
534
534
  "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/inc.js"(exports, module) {
535
535
  "use strict";
536
536
  var SemVer = require_semver();
537
- var inc = (version, release, options, identifier2, identifierBase) => {
537
+ var inc2 = (version, release, options, identifier2, identifierBase) => {
538
538
  if (typeof options === "string") {
539
539
  identifierBase = identifier2;
540
540
  identifier2 = options;
@@ -549,7 +549,7 @@ var require_inc = __commonJS({
549
549
  return null;
550
550
  }
551
551
  };
552
- module.exports = inc;
552
+ module.exports = inc2;
553
553
  }
554
554
  });
555
555
 
@@ -1874,7 +1874,7 @@ var require_semver2 = __commonJS({
1874
1874
  var parse = require_parse();
1875
1875
  var valid2 = require_valid();
1876
1876
  var clean = require_clean();
1877
- var inc = require_inc();
1877
+ var inc2 = require_inc();
1878
1878
  var diff = require_diff();
1879
1879
  var major = require_major();
1880
1880
  var minor = require_minor();
@@ -1912,7 +1912,7 @@ var require_semver2 = __commonJS({
1912
1912
  parse,
1913
1913
  valid: valid2,
1914
1914
  clean,
1915
- inc,
1915
+ inc: inc2,
1916
1916
  diff,
1917
1917
  major,
1918
1918
  minor,
@@ -1974,132 +1974,12 @@ var RegistryError = class extends Error {
1974
1974
  }
1975
1975
  };
1976
1976
 
1977
+ // ../kernel/src/acl.ts
1978
+ var ALLOWED = Object.freeze({ allowed: true });
1979
+ var NO_POLICIES = Object.freeze([]);
1980
+
1977
1981
  // ../kernel/src/module-compatibility.ts
1978
1982
  var import_semver = __toESM(require_semver2(), 1);
1979
- var PLATFORM_API_VERSION = "0.1.0";
1980
- function satisfiesModuleVersion(version, range) {
1981
- return (0, import_semver.valid)(version) !== null && (0, import_semver.validRange)(range) !== null && (0, import_semver.satisfies)(version, range);
1982
- }
1983
- function compareModuleVersions(left, right) {
1984
- return (0, import_semver.rcompare)(left, right);
1985
- }
1986
- function assertModuleCompatibility(manifest, platformVersion = PLATFORM_API_VERSION) {
1987
- if (!(0, import_semver.valid)(manifest.version))
1988
- throw new RegistryError(
1989
- "MODULE_VERSION_INVALID",
1990
- `Invalid version for ${manifest.id}: ${manifest.version}`
1991
- );
1992
- if (manifest.platformApi !== void 0 && (!(0, import_semver.validRange)(manifest.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest.platformApi))) {
1993
- throw new RegistryError(
1994
- "MODULE_PLATFORM_INCOMPATIBLE",
1995
- `${manifest.id} requires platform API ${manifest.platformApi}; available ${PLATFORM_API_VERSION}.`
1996
- );
1997
- }
1998
- const ids = /* @__PURE__ */ new Set();
1999
- for (const dependency of manifest.dependencies) {
2000
- if (!dependency.range.trim() || !(0, import_semver.validRange)(dependency.range))
2001
- throw new RegistryError(
2002
- "MODULE_RANGE_INVALID",
2003
- `${manifest.id} declares an invalid range for ${dependency.id}: ${dependency.range}`
2004
- );
2005
- if (ids.has(dependency.id))
2006
- throw new RegistryError(
2007
- "MODULE_DEPENDENCY_DUPLICATE",
2008
- `${manifest.id} declares ${dependency.id} more than once.`
2009
- );
2010
- ids.add(dependency.id);
2011
- }
2012
- }
2013
- function assertModuleDependency(consumer, dependency, version) {
2014
- if (!satisfiesModuleVersion(version, dependency.range)) {
2015
- throw new RegistryError(
2016
- "MODULE_DEPENDENCY_INCOMPATIBLE",
2017
- `${consumer} requires ${dependency.id} ${dependency.range}; available ${version}.`
2018
- );
2019
- }
2020
- }
2021
-
2022
- // ../kernel/src/module-registry.ts
2023
- function compareModules(left, right) {
2024
- return left.manifest.id.localeCompare(right.manifest.id);
2025
- }
2026
- function visitModule(id, modules, visiting, visited, ordered) {
2027
- if (visited.has(id)) return;
2028
- if (visiting.has(id)) {
2029
- throw new RegistryError(
2030
- "MODULE_DEPENDENCY_CYCLE",
2031
- `Dependency cycle detected at "${id}".`
2032
- );
2033
- }
2034
- const current = modules.get(id);
2035
- if (!current) {
2036
- throw new RegistryError(
2037
- "MODULE_NOT_FOUND",
2038
- `Module "${id}" is not registered.`
2039
- );
2040
- }
2041
- visiting.add(id);
2042
- for (const dependency of [...current.manifest.dependencies].sort(
2043
- (a, b) => a.id.localeCompare(b.id)
2044
- )) {
2045
- if (!modules.has(dependency.id)) {
2046
- throw new RegistryError(
2047
- "MODULE_DEPENDENCY_MISSING",
2048
- `Module "${id}" requires missing module "${dependency.id}".`
2049
- );
2050
- }
2051
- assertModuleDependency(
2052
- id,
2053
- dependency,
2054
- modules.get(dependency.id).manifest.version
2055
- );
2056
- visitModule(dependency.id, modules, visiting, visited, ordered);
2057
- }
2058
- visiting.delete(id);
2059
- visited.add(id);
2060
- ordered.push(current);
2061
- }
2062
- function createModuleRegistry(input) {
2063
- const byId = /* @__PURE__ */ new Map();
2064
- const navigationIds = /* @__PURE__ */ new Set();
2065
- for (const module of input) {
2066
- assertModuleCompatibility(module.manifest);
2067
- if (byId.has(module.manifest.id)) {
2068
- throw new RegistryError(
2069
- "MODULE_DUPLICATE",
2070
- `Module "${module.manifest.id}" is registered more than once.`
2071
- );
2072
- }
2073
- byId.set(module.manifest.id, module);
2074
- for (const item of module.navigation ?? []) {
2075
- if (navigationIds.has(item.id)) {
2076
- throw new RegistryError(
2077
- "NAVIGATION_DUPLICATE",
2078
- `Navigation contribution "${item.id}" is registered more than once.`
2079
- );
2080
- }
2081
- navigationIds.add(item.id);
2082
- }
2083
- }
2084
- const ordered = [];
2085
- const visiting = /* @__PURE__ */ new Set();
2086
- const visited = /* @__PURE__ */ new Set();
2087
- for (const module of [...input].sort(compareModules)) {
2088
- visitModule(module.manifest.id, byId, visiting, visited, ordered);
2089
- }
2090
- return Object.freeze({
2091
- modules: Object.freeze(ordered),
2092
- get: (id) => byId.get(id),
2093
- has: (id) => byId.has(id),
2094
- navigation: (permissions) => Object.freeze(
2095
- ordered.flatMap((module) => module.navigation ?? []).filter(
2096
- (item) => !item.permission || permissions?.has(item.permission)
2097
- ).sort(
2098
- (left, right) => (left.order ?? 1e3) - (right.order ?? 1e3) || left.id.localeCompare(right.id)
2099
- )
2100
- )
2101
- });
2102
- }
2103
1983
 
2104
1984
  // ../contracts/schemas/blueprint.schema.json
2105
1985
  var blueprint_schema_default = {
@@ -2315,6 +2195,31 @@ var module_schema_default = {
2315
2195
  }
2316
2196
  }
2317
2197
  },
2198
+ provides: {
2199
+ type: "array",
2200
+ items: {
2201
+ type: "string",
2202
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2203
+ },
2204
+ uniqueItems: true
2205
+ },
2206
+ requires: {
2207
+ type: "array",
2208
+ items: {
2209
+ type: "object",
2210
+ additionalProperties: false,
2211
+ required: ["id"],
2212
+ properties: {
2213
+ id: {
2214
+ type: "string",
2215
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2216
+ },
2217
+ optional: {
2218
+ type: "boolean"
2219
+ }
2220
+ }
2221
+ }
2222
+ },
2318
2223
  tenancy: {
2319
2224
  enum: ["required", "optional", "none"]
2320
2225
  },
@@ -2422,7 +2327,7 @@ var module_spec_schema_default = {
2422
2327
  ],
2423
2328
  properties: {
2424
2329
  $schema: { type: "string" },
2425
- schemaVersion: { const: 1 },
2330
+ schemaVersion: { enum: [1, 2] },
2426
2331
  id: {
2427
2332
  type: "string",
2428
2333
  pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
@@ -2463,6 +2368,29 @@ var module_spec_schema_default = {
2463
2368
  }
2464
2369
  }
2465
2370
  },
2371
+ provides: {
2372
+ type: "array",
2373
+ items: {
2374
+ type: "string",
2375
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2376
+ },
2377
+ uniqueItems: true
2378
+ },
2379
+ requires: {
2380
+ type: "array",
2381
+ items: {
2382
+ type: "object",
2383
+ additionalProperties: false,
2384
+ required: ["id"],
2385
+ properties: {
2386
+ id: {
2387
+ type: "string",
2388
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2389
+ },
2390
+ optional: { type: "boolean" }
2391
+ }
2392
+ }
2393
+ },
2466
2394
  tenancy: { enum: ["required", "optional", "none"] },
2467
2395
  locales: {
2468
2396
  type: "array",
@@ -2510,6 +2438,269 @@ var module_spec_schema_default = {
2510
2438
  }
2511
2439
  },
2512
2440
  uniqueItems: true
2441
+ },
2442
+ entities: {
2443
+ type: "array",
2444
+ items: { $ref: "#/$defs/entity" },
2445
+ uniqueItems: true
2446
+ },
2447
+ screens: {
2448
+ type: "array",
2449
+ items: { $ref: "#/$defs/screen" },
2450
+ uniqueItems: true
2451
+ },
2452
+ actions: {
2453
+ type: "array",
2454
+ items: { $ref: "#/$defs/action" },
2455
+ uniqueItems: true
2456
+ },
2457
+ widgets: {
2458
+ type: "array",
2459
+ items: { $ref: "#/$defs/widget" },
2460
+ uniqueItems: true
2461
+ },
2462
+ settings: {
2463
+ type: "array",
2464
+ items: { $ref: "#/$defs/setting" },
2465
+ uniqueItems: true
2466
+ },
2467
+ agentTools: {
2468
+ type: "array",
2469
+ items: { $ref: "#/$defs/agentTool" },
2470
+ uniqueItems: true
2471
+ },
2472
+ outOfScope: {
2473
+ type: "array",
2474
+ items: { type: "string", minLength: 1 },
2475
+ uniqueItems: true
2476
+ },
2477
+ decisions: {
2478
+ type: "array",
2479
+ items: { $ref: "#/$defs/decision" },
2480
+ uniqueItems: true
2481
+ }
2482
+ },
2483
+ allOf: [
2484
+ {
2485
+ if: {
2486
+ required: ["schemaVersion"],
2487
+ properties: { schemaVersion: { const: 1 } }
2488
+ },
2489
+ then: {
2490
+ properties: {
2491
+ entities: false,
2492
+ screens: false,
2493
+ actions: false,
2494
+ widgets: false,
2495
+ settings: false,
2496
+ agentTools: false,
2497
+ outOfScope: false,
2498
+ decisions: false
2499
+ }
2500
+ }
2501
+ }
2502
+ ],
2503
+ $defs: {
2504
+ entityId: { type: "string", pattern: "^[a-z][a-z0-9-]*$" },
2505
+ fieldId: { type: "string", pattern: "^[a-z][a-zA-Z0-9]*$" },
2506
+ enumValue: { type: "string", pattern: "^[a-z][a-z0-9_-]*$" },
2507
+ entity: {
2508
+ type: "object",
2509
+ additionalProperties: false,
2510
+ required: ["id", "name", "fields"],
2511
+ properties: {
2512
+ id: { $ref: "#/$defs/entityId" },
2513
+ name: { type: "string", minLength: 1 },
2514
+ description: { type: "string", minLength: 1 },
2515
+ fields: {
2516
+ type: "array",
2517
+ minItems: 1,
2518
+ items: { $ref: "#/$defs/field" },
2519
+ uniqueItems: true
2520
+ },
2521
+ states: {
2522
+ type: "object",
2523
+ additionalProperties: false,
2524
+ required: ["field", "values"],
2525
+ properties: {
2526
+ field: { $ref: "#/$defs/fieldId" },
2527
+ values: {
2528
+ type: "array",
2529
+ minItems: 1,
2530
+ items: { $ref: "#/$defs/enumValue" },
2531
+ uniqueItems: true
2532
+ },
2533
+ transitions: {
2534
+ type: "array",
2535
+ items: {
2536
+ type: "object",
2537
+ additionalProperties: false,
2538
+ required: ["from", "to"],
2539
+ properties: {
2540
+ from: { type: "string", minLength: 1 },
2541
+ to: { type: "string", minLength: 1 },
2542
+ permission: { type: "string", minLength: 1 }
2543
+ }
2544
+ },
2545
+ uniqueItems: true
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ },
2551
+ field: {
2552
+ type: "object",
2553
+ additionalProperties: false,
2554
+ required: ["id", "type"],
2555
+ properties: {
2556
+ id: { $ref: "#/$defs/fieldId" },
2557
+ type: {
2558
+ enum: [
2559
+ "string",
2560
+ "text",
2561
+ "integer",
2562
+ "decimal",
2563
+ "boolean",
2564
+ "date",
2565
+ "datetime",
2566
+ "enum",
2567
+ "reference",
2568
+ "json"
2569
+ ]
2570
+ },
2571
+ required: { type: "boolean" },
2572
+ unique: { enum: ["tenant", "none"] },
2573
+ maxLength: { type: "integer", minimum: 1 },
2574
+ values: {
2575
+ type: "array",
2576
+ minItems: 1,
2577
+ items: { $ref: "#/$defs/enumValue" },
2578
+ uniqueItems: true
2579
+ },
2580
+ reference: {
2581
+ type: "string",
2582
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)*$"
2583
+ },
2584
+ description: { type: "string", minLength: 1 }
2585
+ }
2586
+ },
2587
+ screen: {
2588
+ type: "object",
2589
+ additionalProperties: false,
2590
+ required: ["id", "kind"],
2591
+ properties: {
2592
+ id: { $ref: "#/$defs/entityId" },
2593
+ kind: { enum: ["list", "record", "form", "dashboard"] },
2594
+ entity: { $ref: "#/$defs/entityId" },
2595
+ title: { type: "string", minLength: 1 },
2596
+ columns: {
2597
+ type: "array",
2598
+ items: { $ref: "#/$defs/fieldId" },
2599
+ uniqueItems: true
2600
+ },
2601
+ filters: {
2602
+ type: "array",
2603
+ items: { $ref: "#/$defs/fieldId" },
2604
+ uniqueItems: true
2605
+ },
2606
+ navigationGroup: { type: "string", minLength: 1 }
2607
+ }
2608
+ },
2609
+ action: {
2610
+ type: "object",
2611
+ additionalProperties: false,
2612
+ required: [
2613
+ "id",
2614
+ "permission",
2615
+ "kind",
2616
+ "risk",
2617
+ "idempotent",
2618
+ "description"
2619
+ ],
2620
+ properties: {
2621
+ id: { $ref: "#/$defs/entityId" },
2622
+ entity: { $ref: "#/$defs/entityId" },
2623
+ permission: {
2624
+ type: "string",
2625
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2626
+ },
2627
+ kind: { enum: ["create", "update", "delete", "custom"] },
2628
+ risk: {
2629
+ enum: ["read", "workspace-write", "external", "destructive"]
2630
+ },
2631
+ idempotent: { type: "boolean" },
2632
+ description: { type: "string", minLength: 1 }
2633
+ }
2634
+ },
2635
+ widget: {
2636
+ type: "object",
2637
+ additionalProperties: false,
2638
+ required: ["id", "slot", "description"],
2639
+ properties: {
2640
+ id: { $ref: "#/$defs/entityId" },
2641
+ slot: {
2642
+ enum: [
2643
+ "dashboard.metrics",
2644
+ "dashboard.main",
2645
+ "dashboard.aside",
2646
+ "topbar.actions"
2647
+ ]
2648
+ },
2649
+ entity: { $ref: "#/$defs/entityId" },
2650
+ description: { type: "string", minLength: 1 }
2651
+ }
2652
+ },
2653
+ setting: {
2654
+ type: "object",
2655
+ additionalProperties: false,
2656
+ required: ["key", "type", "scope", "description"],
2657
+ properties: {
2658
+ key: { $ref: "#/$defs/fieldId" },
2659
+ type: { enum: ["string", "integer", "boolean", "enum"] },
2660
+ scope: { enum: ["tenant", "platform"] },
2661
+ default: {
2662
+ oneOf: [
2663
+ { type: "string" },
2664
+ { type: "integer" },
2665
+ { type: "boolean" }
2666
+ ]
2667
+ },
2668
+ values: {
2669
+ type: "array",
2670
+ minItems: 1,
2671
+ items: { type: "string", minLength: 1 },
2672
+ uniqueItems: true
2673
+ },
2674
+ description: { type: "string", minLength: 1 }
2675
+ }
2676
+ },
2677
+ agentTool: {
2678
+ type: "object",
2679
+ additionalProperties: false,
2680
+ required: ["id", "permission", "description", "risk"],
2681
+ properties: {
2682
+ id: {
2683
+ type: "string",
2684
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2685
+ },
2686
+ permission: {
2687
+ type: "string",
2688
+ pattern: "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
2689
+ },
2690
+ description: { type: "string", minLength: 1 },
2691
+ risk: { enum: ["read", "workspace-write"] }
2692
+ }
2693
+ },
2694
+ decision: {
2695
+ type: "object",
2696
+ additionalProperties: false,
2697
+ required: ["id", "question", "answer", "decidedBy"],
2698
+ properties: {
2699
+ id: { type: "string", pattern: "^[A-Z][A-Z0-9-]+$" },
2700
+ question: { type: "string", minLength: 1 },
2701
+ answer: { type: "string", minLength: 1 },
2702
+ decidedBy: { enum: ["user", "default"] }
2703
+ }
2513
2704
  }
2514
2705
  }
2515
2706
  };
@@ -2923,6 +3114,172 @@ var projectSchema = project_schema_default;
2923
3114
  var moduleCatalogSchema = module_catalog_schema_default;
2924
3115
  var moduleArtifactSchema = module_artifact_schema_default;
2925
3116
 
3117
+ // ../contracts/src/index.ts
3118
+ var PLATFORM_API_VERSION = "0.1.6";
3119
+
3120
+ // ../kernel/src/module-compatibility.ts
3121
+ function satisfiesModuleVersion(version, range) {
3122
+ return (0, import_semver.valid)(version) !== null && (0, import_semver.validRange)(range) !== null && (0, import_semver.satisfies)(version, range);
3123
+ }
3124
+ function compareModuleVersions(left, right) {
3125
+ return (0, import_semver.rcompare)(left, right);
3126
+ }
3127
+ function assertModuleCompatibility(manifest, platformVersion = PLATFORM_API_VERSION) {
3128
+ if (!(0, import_semver.valid)(manifest.version))
3129
+ throw new RegistryError(
3130
+ "MODULE_VERSION_INVALID",
3131
+ `Invalid version for ${manifest.id}: ${manifest.version}`
3132
+ );
3133
+ if (manifest.platformApi !== void 0 && (!(0, import_semver.validRange)(manifest.platformApi) || platformVersion !== null && !satisfiesModuleVersion(platformVersion, manifest.platformApi))) {
3134
+ throw new RegistryError(
3135
+ "MODULE_PLATFORM_INCOMPATIBLE",
3136
+ `${manifest.id} requires platform API ${manifest.platformApi}; available ${PLATFORM_API_VERSION}.`
3137
+ );
3138
+ }
3139
+ const ids = /* @__PURE__ */ new Set();
3140
+ for (const dependency of manifest.dependencies) {
3141
+ if (!dependency.range.trim() || !(0, import_semver.validRange)(dependency.range))
3142
+ throw new RegistryError(
3143
+ "MODULE_RANGE_INVALID",
3144
+ `${manifest.id} declares an invalid range for ${dependency.id}: ${dependency.range}`
3145
+ );
3146
+ if (ids.has(dependency.id))
3147
+ throw new RegistryError(
3148
+ "MODULE_DEPENDENCY_DUPLICATE",
3149
+ `${manifest.id} declares ${dependency.id} more than once.`
3150
+ );
3151
+ ids.add(dependency.id);
3152
+ }
3153
+ }
3154
+ function assertModuleDependency(consumer, dependency, version) {
3155
+ if (!satisfiesModuleVersion(version, dependency.range)) {
3156
+ throw new RegistryError(
3157
+ "MODULE_DEPENDENCY_INCOMPATIBLE",
3158
+ `${consumer} requires ${dependency.id} ${dependency.range}; available ${version}.`
3159
+ );
3160
+ }
3161
+ }
3162
+
3163
+ // ../kernel/src/module-registry.ts
3164
+ function compareModules(left, right) {
3165
+ return left.manifest.id.localeCompare(right.manifest.id);
3166
+ }
3167
+ function capabilityProviders(modules) {
3168
+ const providers = /* @__PURE__ */ new Map();
3169
+ for (const module of modules) {
3170
+ for (const capability of module.manifest.provides ?? []) {
3171
+ const existing = providers.get(capability);
3172
+ if (existing && existing !== module.manifest.id) {
3173
+ throw new RegistryError(
3174
+ "MODULE_CAPABILITY_DUPLICATE",
3175
+ `Capability "${capability}" is provided by both "${existing}" and "${module.manifest.id}".`
3176
+ );
3177
+ }
3178
+ providers.set(capability, module.manifest.id);
3179
+ }
3180
+ }
3181
+ return providers;
3182
+ }
3183
+ function visitModule(id, modules, providers, visiting, visited, ordered) {
3184
+ if (visited.has(id)) return;
3185
+ if (visiting.has(id)) {
3186
+ throw new RegistryError(
3187
+ "MODULE_DEPENDENCY_CYCLE",
3188
+ `Dependency cycle detected at "${id}".`
3189
+ );
3190
+ }
3191
+ const current = modules.get(id);
3192
+ if (!current) {
3193
+ throw new RegistryError(
3194
+ "MODULE_NOT_FOUND",
3195
+ `Module "${id}" is not registered.`
3196
+ );
3197
+ }
3198
+ visiting.add(id);
3199
+ for (const dependency of [...current.manifest.dependencies].sort(
3200
+ (a, b) => a.id.localeCompare(b.id)
3201
+ )) {
3202
+ if (!modules.has(dependency.id)) {
3203
+ throw new RegistryError(
3204
+ "MODULE_DEPENDENCY_MISSING",
3205
+ `Module "${id}" requires missing module "${dependency.id}".`
3206
+ );
3207
+ }
3208
+ assertModuleDependency(
3209
+ id,
3210
+ dependency,
3211
+ modules.get(dependency.id).manifest.version
3212
+ );
3213
+ visitModule(dependency.id, modules, providers, visiting, visited, ordered);
3214
+ }
3215
+ for (const requirement of [...current.manifest.requires ?? []].sort(
3216
+ (a, b) => a.id.localeCompare(b.id)
3217
+ )) {
3218
+ if (requirement.optional) continue;
3219
+ const provider = providers.get(requirement.id);
3220
+ if (!provider) {
3221
+ throw new RegistryError(
3222
+ "MODULE_CAPABILITY_MISSING",
3223
+ `Module "${id}" requires capability "${requirement.id}" and no registered module provides it.`
3224
+ );
3225
+ }
3226
+ if (provider === id) continue;
3227
+ visitModule(provider, modules, providers, visiting, visited, ordered);
3228
+ }
3229
+ visiting.delete(id);
3230
+ visited.add(id);
3231
+ ordered.push(current);
3232
+ }
3233
+ function createModuleRegistry(input) {
3234
+ const byId = /* @__PURE__ */ new Map();
3235
+ const navigationIds = /* @__PURE__ */ new Set();
3236
+ for (const module of input) {
3237
+ assertModuleCompatibility(module.manifest);
3238
+ if (byId.has(module.manifest.id)) {
3239
+ throw new RegistryError(
3240
+ "MODULE_DUPLICATE",
3241
+ `Module "${module.manifest.id}" is registered more than once.`
3242
+ );
3243
+ }
3244
+ byId.set(module.manifest.id, module);
3245
+ for (const item of module.navigation ?? []) {
3246
+ if (navigationIds.has(item.id)) {
3247
+ throw new RegistryError(
3248
+ "NAVIGATION_DUPLICATE",
3249
+ `Navigation contribution "${item.id}" is registered more than once.`
3250
+ );
3251
+ }
3252
+ navigationIds.add(item.id);
3253
+ }
3254
+ }
3255
+ const providers = capabilityProviders(byId.values());
3256
+ const ordered = [];
3257
+ const visiting = /* @__PURE__ */ new Set();
3258
+ const visited = /* @__PURE__ */ new Set();
3259
+ for (const module of [...input].sort(compareModules)) {
3260
+ visitModule(
3261
+ module.manifest.id,
3262
+ byId,
3263
+ providers,
3264
+ visiting,
3265
+ visited,
3266
+ ordered
3267
+ );
3268
+ }
3269
+ return Object.freeze({
3270
+ modules: Object.freeze(ordered),
3271
+ get: (id) => byId.get(id),
3272
+ has: (id) => byId.has(id),
3273
+ navigation: (permissions) => Object.freeze(
3274
+ ordered.flatMap((module) => module.navigation ?? []).filter(
3275
+ (item) => !item.permission || permissions?.has(item.permission)
3276
+ ).sort(
3277
+ (left, right) => (left.order ?? 1e3) - (right.order ?? 1e3) || left.id.localeCompare(right.id)
3278
+ )
3279
+ )
3280
+ });
3281
+ }
3282
+
2926
3283
  // src/validation.ts
2927
3284
  import { readdir, readFile } from "node:fs/promises";
2928
3285
  import { basename, dirname, join, relative } from "node:path";
@@ -3339,6 +3696,26 @@ function resolveModuleReleases(catalog, target, installed) {
3339
3696
  entries.sort(
3340
3697
  (a, b) => compareModuleVersions(a.manifest.version, b.manifest.version)
3341
3698
  );
3699
+ const providers = /* @__PURE__ */ new Map();
3700
+ for (const entries of candidates.values())
3701
+ for (const release of entries)
3702
+ for (const capability of release.manifest.provides ?? []) {
3703
+ const list = providers.get(capability) ?? [];
3704
+ list.push(release);
3705
+ providers.set(capability, list);
3706
+ }
3707
+ function wanted(manifest) {
3708
+ return [
3709
+ ...manifest.dependencies,
3710
+ ...(manifest.requires ?? []).filter((requirement) => !requirement.optional).map((requirement) => ({ capability: requirement.id }))
3711
+ ];
3712
+ }
3713
+ function platformCompatible(candidate) {
3714
+ return satisfiesModuleVersion(
3715
+ PLATFORM_API_VERSION,
3716
+ candidate.manifest.platformApi
3717
+ );
3718
+ }
3342
3719
  let attempts = 0;
3343
3720
  function search(selected2, pending) {
3344
3721
  distributionAssert(
@@ -3348,17 +3725,31 @@ function resolveModuleReleases(catalog, target, installed) {
3348
3725
  );
3349
3726
  const [next, ...rest] = pending;
3350
3727
  if (!next) return selected2;
3728
+ if ("capability" in next) {
3729
+ const provided = [
3730
+ ...installed,
3731
+ ...[...selected2.values()].map((release) => release.manifest)
3732
+ ].some((manifest) => manifest.provides?.includes(next.capability));
3733
+ if (provided) return search(selected2, rest);
3734
+ for (const candidate of providers.get(next.capability) ?? []) {
3735
+ if (selected2.has(candidate.manifest.id) || existing.has(candidate.manifest.id) || !platformCompatible(candidate))
3736
+ continue;
3737
+ const found = search(
3738
+ new Map([...selected2, [candidate.manifest.id, candidate]]),
3739
+ [...wanted(candidate.manifest), ...rest]
3740
+ );
3741
+ if (found) return found;
3742
+ }
3743
+ return void 0;
3744
+ }
3351
3745
  const fixed = selected2.get(next.id)?.manifest ?? existing.get(next.id);
3352
3746
  if (fixed)
3353
3747
  return satisfiesModuleVersion(fixed.version, next.range) ? search(selected2, rest) : void 0;
3354
3748
  for (const candidate of candidates.get(next.id) ?? []) {
3355
- if (!satisfiesModuleVersion(candidate.manifest.version, next.range) || !satisfiesModuleVersion(
3356
- PLATFORM_API_VERSION,
3357
- candidate.manifest.platformApi
3358
- ))
3749
+ if (!satisfiesModuleVersion(candidate.manifest.version, next.range) || !platformCompatible(candidate))
3359
3750
  continue;
3360
3751
  const found = search(new Map([...selected2, [next.id, candidate]]), [
3361
- ...candidate.manifest.dependencies,
3752
+ ...wanted(candidate.manifest),
3362
3753
  ...rest
3363
3754
  ]);
3364
3755
  if (found) return found;
@@ -3405,7 +3796,7 @@ import { dirname as dirname6, join as join6, relative as relative5 } from "node:
3405
3796
  import { createRequire } from "node:module";
3406
3797
  import { readFile as readFile5 } from "node:fs/promises";
3407
3798
  import { dirname as dirname4, join as join3 } from "node:path";
3408
- var SDK_VERSION = "0.2.3";
3799
+ var SDK_VERSION = "0.3.0";
3409
3800
  function npmPackage(specifier) {
3410
3801
  return specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
3411
3802
  }
@@ -3560,6 +3951,15 @@ async function platformIssues(moduleRoot, manifest) {
3560
3951
  )
3561
3952
  );
3562
3953
  }
3954
+ if (manifest.platformApi === void 0) {
3955
+ issues.push(
3956
+ issue(
3957
+ "PLATFORM_API_MISSING",
3958
+ `module.json declares no platformApi; add "platformApi": "^${PLATFORM_API_VERSION}" so the platform contract the module was built against is checked.`,
3959
+ "module.json"
3960
+ )
3961
+ );
3962
+ }
3563
3963
  if (packageVersion !== manifest.version) {
3564
3964
  issues.push(
3565
3965
  issue(
@@ -3648,6 +4048,26 @@ async function specIssues(moduleRoot, manifest, specDirectory) {
3648
4048
  )
3649
4049
  );
3650
4050
  }
4051
+ if (Array.isArray(spec.dependencies)) {
4052
+ const declared = spec.dependencies.map((entry) => `${String(entry.id)}@${String(entry.range)}`);
4053
+ const actual = manifest.dependencies.map(
4054
+ (entry) => `${entry.id}@${entry.range}`
4055
+ );
4056
+ const drift = [
4057
+ ...declared.filter((entry) => !actual.includes(entry)),
4058
+ ...actual.filter((entry) => !declared.includes(entry))
4059
+ ];
4060
+ if (drift.length > 0) {
4061
+ issues.push(
4062
+ issue(
4063
+ "SPEC_DEPENDENCY_DRIFT",
4064
+ `Specification dependencies differ from module.json: ${drift.join(", ")}. Run module version bump or align the ranges by hand.`,
4065
+ specPath,
4066
+ "warning"
4067
+ )
4068
+ );
4069
+ }
4070
+ }
3651
4071
  return issues;
3652
4072
  }
3653
4073
  async function translationIssues(moduleRoot, manifest, projectLocales) {
@@ -4258,13 +4678,23 @@ function platformImport(id, pkg, entry) {
4258
4678
  function clientImport(id, pkg) {
4259
4679
  return `import { createClientContribution as ${identifier(id)} } from '${pkg}/client';`;
4260
4680
  }
4681
+ function capabilityScope(module) {
4682
+ if (!module.manifest) return "";
4683
+ const declaration = JSON.stringify({
4684
+ provides: module.manifest.provides ?? [],
4685
+ requires: module.manifest.requires ?? []
4686
+ });
4687
+ return ` capabilities: context.capabilities.forModule('${module.id}', ${declaration}),
4688
+ `;
4689
+ }
4261
4690
  function generateServerComposition(modules, sdk = false, mounts = [], applicationPath = "/app") {
4262
4691
  const server = modules.filter((module) => module.server);
4692
+ const serverPackage = sdk ? "@flowdular/sdk/server" : "@flowdular/server";
4263
4693
  const typeImport = `import type {
4264
4694
  PlatformServerComposition,
4265
4695
  PlatformServerContext,
4266
4696
  } from '${sdk ? "@flowdular/sdk/modules/auth" : "@flowdular/module-auth"}/server';
4267
- import type { WebMount } from '${sdk ? "@flowdular/sdk/server" : "@flowdular/server"}';
4697
+ import type { WebMount } from '${serverPackage}';
4268
4698
  `;
4269
4699
  if (server.length === 0) {
4270
4700
  return GENERATED_HEADER + typeImport + "\nexport function composeModuleServer(\n _context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [];\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
@@ -4276,9 +4706,12 @@ import type { WebMount } from '${sdk ? "@flowdular/sdk/server" : "@flowdular/ser
4276
4706
  (module) => ` { ...${identifier(module.id)}({
4277
4707
  ...context,
4278
4708
  agentDefinitions: context.agentDefinitions.forModule('${module.id}'),
4709
+ dataClasses: context.dataClasses.forModule('${module.id}'),
4710
+ ` + capabilityScope(module) + ` metrics: createModuleMetrics('${module.id}'),
4279
4711
  }), moduleId: '${module.id}' },`
4280
4712
  ).join("\n");
4281
- return GENERATED_HEADER + typeImport + imports + "\n\nexport function composeModuleServer(\n context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [\n" + calls + "\n ];\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
4713
+ return GENERATED_HEADER + typeImport + `import { createModuleMetrics } from '${serverPackage}';
4714
+ ` + imports + "\n\nexport function composeModuleServer(\n context: PlatformServerContext,\n): readonly PlatformServerComposition[] {\n return [\n" + calls + "\n ];\n}\n\nexport const moduleWebMounts: readonly WebMount[] = " + JSON.stringify(mounts) + " as const;\nexport const applicationBasePath = " + JSON.stringify(applicationPath) + ";\n";
4282
4715
  }
4283
4716
  function generateClientComposition(modules, sdk = false, applicationPath = "/app") {
4284
4717
  const client = modules.filter((module) => module.client);
@@ -4324,6 +4757,11 @@ function moduleOrder(manifests, roots, includeDependencies) {
4324
4757
  const visiting = [];
4325
4758
  const visited = /* @__PURE__ */ new Set();
4326
4759
  const ordered = [];
4760
+ const providers = capabilityProviders(
4761
+ [...manifests.values()].flatMap(
4762
+ (descriptor) => descriptor.manifest ? [{ manifest: descriptor.manifest }] : []
4763
+ )
4764
+ );
4327
4765
  const visit = (id, requiredBy) => {
4328
4766
  if (visited.has(id)) return;
4329
4767
  const cycleStart = visiting.indexOf(id);
@@ -4357,6 +4795,24 @@ function moduleOrder(manifests, roots, includeDependencies) {
4357
4795
  assertModuleDependency(id, constraint, version);
4358
4796
  visit(dependency, id);
4359
4797
  }
4798
+ for (const requirement of [...current.manifest?.requires ?? []].sort(
4799
+ (left, right) => left.id.localeCompare(right.id)
4800
+ )) {
4801
+ if (requirement.optional) continue;
4802
+ const provider = providers.get(requirement.id);
4803
+ if (!provider) {
4804
+ throw new Error(
4805
+ `Module "${id}" requires capability "${requirement.id}" and no module provides it.`
4806
+ );
4807
+ }
4808
+ if (provider === id) continue;
4809
+ if (!includeDependencies && !requested.has(provider)) {
4810
+ throw new Error(
4811
+ `Enabled module "${id}" requires capability "${requirement.id}" from disabled module "${provider}". Enable the provider first.`
4812
+ );
4813
+ }
4814
+ visit(provider, id);
4815
+ }
4360
4816
  visiting.pop();
4361
4817
  visited.add(id);
4362
4818
  ordered.push(current);