@wairon/cli 5.1.1-dev.69 → 5.1.1-dev.70

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 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.69";
68
+ WAIRON_VERSION = "5.1.1-dev.70";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  SUPPORTED_ALIASES = ["wai"];
71
71
  SCAN_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
@@ -14374,6 +14374,135 @@ var init_param_conformance = __esm({
14374
14374
  }
14375
14375
  });
14376
14376
 
14377
+ // src/core/rules/conformance/route-coverage.ts
14378
+ function liesUnder2(path71, prefix) {
14379
+ return path71 === prefix || path71.startsWith(prefix + "/");
14380
+ }
14381
+ function normalizeSegment(segment) {
14382
+ return /^\{.+\}$/.test(segment) || segment.startsWith(":") ? "*" : segment;
14383
+ }
14384
+ function completed(route, heads) {
14385
+ const variants = route.segments[0] === "*" && heads.length > 0 ? heads.map((head) => [head, ...route.segments.slice(1)]) : [route.segments];
14386
+ return variants.map((segments) => ({
14387
+ verb: route.verb.toUpperCase(),
14388
+ segments: segments.map(normalizeSegment),
14389
+ exactLength: route.exactLength,
14390
+ key: `${route.verb.toUpperCase()} /${segments.join("/")}${route.exactLength ? "" : "/\u2026"}`
14391
+ }));
14392
+ }
14393
+ function answers(route, endpoint) {
14394
+ if (route.verb !== endpoint.verb) return false;
14395
+ if (route.exactLength ? endpoint.segments.length !== route.segments.length : endpoint.segments.length < route.segments.length) return false;
14396
+ return route.segments.every((segment, index) => segment === endpoint.segments[index]);
14397
+ }
14398
+ var routeCoverageRule;
14399
+ var init_route_coverage = __esm({
14400
+ "src/core/rules/conformance/route-coverage.ts"() {
14401
+ "use strict";
14402
+ init_models();
14403
+ routeCoverageRule = {
14404
+ name: "route-coverage",
14405
+ description: "Code-to-contract for the ROUTES: does every route a router actually answers have a contract endpoint, and does every contract endpoint have a route that answers it? A portal's endpoints are what its contract promises; the router is what the code serves; nothing compared the two, so a route with no contract \u2014 including a write \u2014 could run for months without a single rule noticing. The listener's mount names the router function (its `via`), and the routes are read out of that function's guards; the path segment the router never checks is the one the mount's prefix guarantees. One idiom is read \u2014 a method comparison with comparisons on the path's split segments \u2014 and a mounted router that yields no route in it is reported as unread, never passed: a check that cannot see a router must say so rather than stay quiet.",
14406
+ codes: [
14407
+ {
14408
+ code: "UNDECLARED_ROUTE",
14409
+ defaultSeverity: "warning",
14410
+ summary: "A router answers a route that no contract endpoint of the portal it serves declares \u2014 a surface the code serves and the design never promised",
14411
+ // Measured code↔contract drift, route by route, at the mount's router
14412
+ // entry — which is why every one of them hands over `parts`.
14413
+ carryable: true
14414
+ },
14415
+ {
14416
+ code: "UNROUTED_ENDPOINT",
14417
+ defaultSeverity: "warning",
14418
+ summary: "A contract endpoint no route of the portal's router answers \u2014 the contract promises a route that no request can reach",
14419
+ carryable: true
14420
+ },
14421
+ {
14422
+ code: "UNREADABLE_ROUTER",
14423
+ defaultSeverity: "warning",
14424
+ summary: "A mounted portal's router yields no route this analysis can read, so its routes were not checked against the contract at all",
14425
+ carryable: true
14426
+ }
14427
+ ],
14428
+ check(ctx) {
14429
+ const code = ctx.codeIndex();
14430
+ const realization = ctx.realizationIndex();
14431
+ for (const listener of ctx.components) {
14432
+ if (listener.componentType !== "Portal") continue;
14433
+ for (const mount of listener.mounts ?? []) {
14434
+ const via = mount.via;
14435
+ if (!via) continue;
14436
+ const portal = ctx.componentMap.get(mount.portal);
14437
+ if (!portal || portal.componentType !== "Portal") continue;
14438
+ const [anchor] = realization.implementationsOf(portal.id).map((impl) => impl.id).sort();
14439
+ if (!anchor) continue;
14440
+ const holders = realization.filesOf(portal.id).map(pathKey).filter((file) => {
14441
+ const facts = code.factsAt(file);
14442
+ return !!facts && facts.status === "analyzed" && facts.analysisGrade === "exact" && !!facts.functionParams && Object.prototype.hasOwnProperty.call(facts.functionParams, via);
14443
+ });
14444
+ if (holders.length === 0) continue;
14445
+ const read = holders.flatMap((file) => {
14446
+ const routes = code.factsAt(file).functionRoutes;
14447
+ return routes && Object.prototype.hasOwnProperty.call(routes, via) ? routes[via] : [];
14448
+ });
14449
+ const draftContext = ctx.isComponentDraft(listener.id) || ctx.isComponentDraft(portal.id);
14450
+ const where = holders.map((file) => `"${file}"`).join(", ");
14451
+ if (read.length === 0) {
14452
+ ctx.addIssue(
14453
+ "warning",
14454
+ "UNREADABLE_ROUTER",
14455
+ `Listener "${listener.id}" mounts portal "${portal.id}" through "${via}" in ${where}, but no branch of "${via}" reads as a route \u2014 so none of its routes were checked against the contract at all. Only one idiom is read: an \`if\` whose conditions, together with those of every enclosing \`if\`, compare \`<request>.method\` with a string and \`parts[i]\` / \`parts.length\` with literals. Silence here would read as a clean router; it is only one this analysis cannot see. Write the router in that idiom, or carry this finding with the reason it cannot be.`,
14456
+ anchor,
14457
+ draftContext,
14458
+ void 0,
14459
+ { at: via }
14460
+ );
14461
+ continue;
14462
+ }
14463
+ const heads = [...new Set(mount.prefixes.map((prefix) => prefix.split("/").filter(Boolean)[0]).filter((head) => !!head))];
14464
+ const served = read.map((route) => completed(route, heads));
14465
+ const endpoints = ctx.interfaceMethodsOf(portal.id).flatMap((method2) => {
14466
+ const endpoint = method2.endpoint;
14467
+ if (endpoint?.transport !== "HTTP") return [];
14468
+ if (!mount.prefixes.some((prefix) => liesUnder2(endpoint.path, prefix))) return [];
14469
+ return [{
14470
+ verb: endpoint.method.toUpperCase(),
14471
+ segments: endpoint.path.split("/").filter(Boolean).map(normalizeSegment),
14472
+ key: `${endpoint.method.toUpperCase()} ${endpoint.path}`
14473
+ }];
14474
+ });
14475
+ const undeclared = [...new Set(served.filter((variants) => !variants.some((route) => endpoints.some((endpoint) => answers(route, endpoint)))).flatMap((variants) => variants.map((route) => route.key)))].sort();
14476
+ if (undeclared.length > 0) {
14477
+ ctx.addIssue(
14478
+ "warning",
14479
+ "UNDECLARED_ROUTE",
14480
+ `Portal "${portal.id}"'s router "${via}" (${where}), mounted by listener "${listener.id}", answers ${undeclared.length} route(s) no contract endpoint of "${portal.id}" declares \u2014 ${undeclared.map((key) => `"${key}"`).join(", ")}. A surface the code serves and the design never promised is how a write runs with no contract: no brief, no auth review, no rule reading it. Declare each as a contract method with its endpoint, or take the route out of the router.`,
14481
+ anchor,
14482
+ draftContext,
14483
+ void 0,
14484
+ { at: via, covers: undeclared }
14485
+ );
14486
+ }
14487
+ const unrouted = [...new Set(endpoints.filter((endpoint) => !served.some((variants) => variants.some((route) => answers(route, endpoint)))).map((endpoint) => endpoint.key))].sort();
14488
+ if (unrouted.length > 0) {
14489
+ ctx.addIssue(
14490
+ "warning",
14491
+ "UNROUTED_ENDPOINT",
14492
+ `Portal "${portal.id}" binds ${unrouted.length} HTTP endpoint(s) under listener "${listener.id}"'s mount that no route of its router "${via}" (${where}) answers \u2014 ${unrouted.map((key) => `"${key}"`).join(", ")}. The contract promises a route no request can reach. Route it in the router, or drop the endpoint from the contract.`,
14493
+ anchor,
14494
+ draftContext,
14495
+ void 0,
14496
+ { at: via, covers: unrouted }
14497
+ );
14498
+ }
14499
+ }
14500
+ }
14501
+ }
14502
+ };
14503
+ }
14504
+ });
14505
+
14377
14506
  // src/core/rules/conformance/unclaimed-source.ts
14378
14507
  var unclaimedSourceRule;
14379
14508
  var init_unclaimed_source = __esm({
@@ -14695,14 +14824,21 @@ function isPureForwarder(methods) {
14695
14824
  const narrated = methods.filter((m) => (m.narrative ?? []).length > 0);
14696
14825
  return narrated.length > 0 && narrated.every((m) => isForwardingMethod(m.narrative ?? []));
14697
14826
  }
14698
- function pureForwarderComponents(ctx) {
14827
+ function reachesCollaborator(narrative) {
14828
+ return narrative.some((step) => step.type === "call" || step.type === "dispatch" || step.type === "register");
14829
+ }
14830
+ function isRoutingTable(methods) {
14831
+ const reaching = methods.map((m) => m.narrative ?? []).filter(reachesCollaborator);
14832
+ return reaching.length > 0 && reaching.every(isForwardingMethod);
14833
+ }
14834
+ function routingTableComponents(ctx) {
14699
14835
  const realized = /* @__PURE__ */ new Map();
14700
14836
  for (const impl of ctx.implementations) {
14701
14837
  const intf = ctx.interfaceMap.get(impl.contract);
14702
14838
  if (!intf) continue;
14703
14839
  realized.set(intf.component, [...realized.get(intf.component) ?? [], ...impl.methods]);
14704
14840
  }
14705
- return new Set([...realized].filter(([, methods]) => isPureForwarder(methods)).map(([id]) => id));
14841
+ return new Set([...realized].filter(([, methods]) => isRoutingTable(methods)).map(([id]) => id));
14706
14842
  }
14707
14843
  var methodCohesionRule;
14708
14844
  var init_method_cohesion = __esm({
@@ -14767,7 +14903,7 @@ var init_coupling_health = __esm({
14767
14903
  { code: "MUTUAL_SUBSYSTEM_DEPENDENCY", defaultSeverity: "warning", summary: "Subsystems depend on each other without a declared trusted link" },
14768
14904
  { code: "INVALID_TRUSTED_LINK", defaultSeverity: "error", summary: "trustedLinks references a non-existent subsystem" },
14769
14905
  { code: "UNUSED_TRUSTED_LINK", defaultSeverity: "warning", summary: "trustedLinks declares a peer no dependency actually reaches" },
14770
- { code: "GOD_COMPONENT", defaultSeverity: "warning", summary: "Component with excessive dependency fan-out; not reported on a pure forwarder, whose every narrated method is a single hand-off" }
14906
+ { code: "GOD_COMPONENT", defaultSeverity: "warning", summary: "Component with excessive dependency fan-out; not reported on a routing table, which reaches every collaborator it has only through single hand-offs" }
14771
14907
  ],
14772
14908
  check(ctx) {
14773
14909
  const subsystemDeps = /* @__PURE__ */ new Map();
@@ -14839,10 +14975,10 @@ var init_coupling_health = __esm({
14839
14975
  );
14840
14976
  }
14841
14977
  }
14842
- const forwarders = pureForwarderComponents(ctx);
14978
+ const routingTables = routingTableComponents(ctx);
14843
14979
  for (const comp of ctx.components) {
14844
14980
  const threshold = ctx.complexityConfigFor(comp.subsystem)?.maxComponentDependencies ?? DEFAULT_GOD_COMPONENT_THRESHOLD;
14845
- if (comp.dependsOn.length > threshold && !forwarders.has(comp.id)) {
14981
+ if (comp.dependsOn.length > threshold && !routingTables.has(comp.id)) {
14846
14982
  ctx.addIssue(
14847
14983
  "warning",
14848
14984
  "GOD_COMPONENT",
@@ -15475,7 +15611,7 @@ var init_complexity_and_metadata = __esm({
15475
15611
  { code: "DESCRIPTION_TOO_SHORT", defaultSeverity: "warning", summary: "Description is shorter than the configured minimum length" },
15476
15612
  { code: "EXCESSIVE_METHODS", defaultSeverity: "warning", summary: "Interface declares more methods than the configured limit" },
15477
15613
  { code: "EXCESSIVE_METHOD_PARAMS", defaultSeverity: "warning", summary: "Interface method declares more parameters than the configured limit" },
15478
- { code: "EXCESSIVE_DEPENDENCIES", defaultSeverity: "warning", summary: "Component has more dependencies than the configured limit; not reported on a pure forwarder, whose fan-out is a routing table rather than knowledge" },
15614
+ { code: "EXCESSIVE_DEPENDENCIES", defaultSeverity: "warning", summary: "Component has more dependencies than the configured limit; not reported on a routing table, which reaches every collaborator it has only through single hand-offs" },
15479
15615
  { code: "EXCESSIVE_SUBSYSTEM_COMPONENTS", defaultSeverity: "warning", summary: "Subsystem has more direct components than the configured limit" }
15480
15616
  ],
15481
15617
  check(ctx) {
@@ -15495,7 +15631,7 @@ var init_complexity_and_metadata = __esm({
15495
15631
  );
15496
15632
  }
15497
15633
  }
15498
- const forwarders = pureForwarderComponents(ctx);
15634
+ const routingTables = routingTableComponents(ctx);
15499
15635
  for (const comp of ctx.components) {
15500
15636
  const docConfig = ctx.documentationConfigFor(comp.subsystem);
15501
15637
  const complexityConfig = ctx.complexityConfigFor(comp.subsystem);
@@ -15509,7 +15645,7 @@ var init_complexity_and_metadata = __esm({
15509
15645
  "Component",
15510
15646
  isDraft
15511
15647
  );
15512
- if (complexityConfig?.maxComponentDependencies !== void 0 && comp.dependsOn.length > complexityConfig.maxComponentDependencies && !forwarders.has(comp.id)) {
15648
+ if (complexityConfig?.maxComponentDependencies !== void 0 && comp.dependsOn.length > complexityConfig.maxComponentDependencies && !routingTables.has(comp.id)) {
15513
15649
  ctx.addIssue(
15514
15650
  "warning",
15515
15651
  "EXCESSIVE_DEPENDENCIES",
@@ -15919,6 +16055,7 @@ var init_repository = __esm({
15919
16055
  init_type_realization();
15920
16056
  init_type_shape();
15921
16057
  init_param_conformance();
16058
+ init_route_coverage();
15922
16059
  init_unclaimed_source();
15923
16060
  init_export_conformance();
15924
16061
  init_carried_debt();
@@ -16098,6 +16235,7 @@ var init_repository = __esm({
16098
16235
  typeRealizationRule,
16099
16236
  typeShapeRule,
16100
16237
  paramConformanceRule,
16238
+ routeCoverageRule,
16101
16239
  unclaimedSourceRule,
16102
16240
  exportConformanceRule,
16103
16241
  couplingRule,
@@ -16267,6 +16405,7 @@ function walkExact(ts, sourceText, fileName) {
16267
16405
  const localTypes = /* @__PURE__ */ new Map();
16268
16406
  const typeShapes = /* @__PURE__ */ new Map();
16269
16407
  const functionParams = /* @__PURE__ */ new Map();
16408
+ const functionRoutes = /* @__PURE__ */ new Map();
16270
16409
  const mutableBindings = /* @__PURE__ */ new Set();
16271
16410
  const schemaConstants = /* @__PURE__ */ new Map();
16272
16411
  const derivedAliases = [];
@@ -16438,6 +16577,67 @@ function walkExact(ts, sourceText, fileName) {
16438
16577
  if (fn.body) count(fn.body);
16439
16578
  return { score, callees };
16440
16579
  };
16580
+ const conjunctsOf = (expression, out = []) => {
16581
+ if (ts.isParenthesizedExpression(expression)) return conjunctsOf(expression.expression, out);
16582
+ if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
16583
+ conjunctsOf(expression.left, out);
16584
+ conjunctsOf(expression.right, out);
16585
+ return out;
16586
+ }
16587
+ out.push(expression);
16588
+ return out;
16589
+ };
16590
+ const isPartsRef = (expression) => ts.isIdentifier(expression) && expression.text === "parts" || ts.isPropertyAccessExpression(expression) && expression.name.text === "parts";
16591
+ const constraintOf = (conjunct) => {
16592
+ if (!ts.isBinaryExpression(conjunct) || conjunct.operatorToken.kind !== ts.SyntaxKind.EqualsEqualsEqualsToken) {
16593
+ return void 0;
16594
+ }
16595
+ const { left, right } = conjunct;
16596
+ if (ts.isPropertyAccessExpression(left) && left.name.text === "method" && ts.isStringLiteral(right)) {
16597
+ return { verb: right.text };
16598
+ }
16599
+ if (ts.isPropertyAccessExpression(left) && left.name.text === "length" && isPartsRef(left.expression) && ts.isNumericLiteral(right)) {
16600
+ return { length: Number(right.text) };
16601
+ }
16602
+ if (ts.isElementAccessExpression(left) && isPartsRef(left.expression) && ts.isNumericLiteral(left.argumentExpression) && ts.isStringLiteral(right)) {
16603
+ return { index: Number(left.argumentExpression.text), literal: right.text };
16604
+ }
16605
+ return void 0;
16606
+ };
16607
+ const routeOf = (conjuncts) => {
16608
+ let verb;
16609
+ let length;
16610
+ const fixed = /* @__PURE__ */ new Map();
16611
+ for (const conjunct of conjuncts) {
16612
+ const constraint = constraintOf(conjunct);
16613
+ if (!constraint) continue;
16614
+ if ("verb" in constraint) verb = constraint.verb;
16615
+ else if ("length" in constraint) length = constraint.length;
16616
+ else fixed.set(constraint.index, constraint.literal);
16617
+ }
16618
+ if (verb === void 0) return void 0;
16619
+ const count = length ?? (fixed.size > 0 ? Math.max(...fixed.keys()) + 1 : 0);
16620
+ const segments = [];
16621
+ for (let index = 0; index < count; index++) segments.push(fixed.get(index) ?? "*");
16622
+ return { verb, segments, exactLength: length !== void 0 };
16623
+ };
16624
+ const collectRoutes = (fn) => {
16625
+ const routes = /* @__PURE__ */ new Map();
16626
+ const walk2 = (node, enclosing) => {
16627
+ if (namedFunctionName(node) !== void 0) return;
16628
+ if (ts.isIfStatement(node)) {
16629
+ const guarded = [...enclosing, ...conjunctsOf(node.expression)];
16630
+ const route = routeOf(guarded);
16631
+ if (route) routes.set(`${route.verb} /${route.segments.join("/")}${route.exactLength ? "" : "/\u2026"}`, route);
16632
+ walk2(node.thenStatement, guarded);
16633
+ if (node.elseStatement) walk2(node.elseStatement, enclosing);
16634
+ return;
16635
+ }
16636
+ ts.forEachChild(node, (child) => walk2(child, enclosing));
16637
+ };
16638
+ if (fn.body) walk2(fn.body, []);
16639
+ return routes;
16640
+ };
16441
16641
  const visit = (node) => {
16442
16642
  const fnName = namedFunctionName(node);
16443
16643
  if (fnName && node.body) {
@@ -16449,6 +16649,12 @@ function walkExact(ts, sourceText, fileName) {
16449
16649
  const signatures = functionParams.get(fnName) ?? [];
16450
16650
  signatures.push(declaredParameters(node));
16451
16651
  functionParams.set(fnName, signatures);
16652
+ const routes = collectRoutes(node);
16653
+ if (routes.size > 0) {
16654
+ const known = functionRoutes.get(fnName) ?? /* @__PURE__ */ new Map();
16655
+ for (const [key, route] of routes) known.set(key, route);
16656
+ functionRoutes.set(fnName, known);
16657
+ }
16452
16658
  }
16453
16659
  if (ts.isPropertyDeclaration(node)) recordFieldType(node.name, node.type);
16454
16660
  else if (ts.isParameter(node) && node.parent && ts.isConstructorDeclaration(node.parent) && isParameterProperty(node)) {
@@ -16556,7 +16762,7 @@ function walkExact(ts, sourceText, fileName) {
16556
16762
  typeShapes.set(alias.name, { origin: "derived", fields: schemaMembers(literal), methods: [] });
16557
16763
  }
16558
16764
  const reexportOnly = sf.statements.length > 0 && sf.statements.every((st) => ts.isExportDeclaration(st) && !!st.moduleSpecifier);
16559
- return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, mutableBindings, reexportOnly };
16765
+ return { declared, anchors, exported, imports, reexports, starExports, namedReexports, complexity, calls, importBindings, typeOnlyBindings, fieldTypes, localTypes, typeShapes, functionParams, functionRoutes, mutableBindings, reexportOnly };
16560
16766
  }
16561
16767
  function resolveRelativeModule(fromFile, specifier) {
16562
16768
  if (!specifier.startsWith(".")) return null;
@@ -16740,6 +16946,7 @@ function buildCodeModel(implementations, types, projectRoot2, sourceRoots = [],
16740
16946
  localTypes: Object.fromEntries([...facts.localTypes].map(([k, v]) => [k, [...v]])),
16741
16947
  typeShapes: Object.fromEntries(facts.typeShapes),
16742
16948
  functionParams: Object.fromEntries(facts.functionParams),
16949
+ functionRoutes: Object.fromEntries([...facts.functionRoutes].map(([k, v]) => [k, [...v.values()]])),
16743
16950
  topLevelMutableBindings: [...facts.mutableBindings],
16744
16951
  reexportOnly: facts.reexportOnly
16745
16952
  };
@@ -33580,7 +33787,7 @@ async function runDomainsAdd(options = {}) {
33580
33787
  type: "input",
33581
33788
  name: "domainName",
33582
33789
  message: "Display name:",
33583
- default: (answers) => answers.domainId.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")
33790
+ default: (answers2) => answers2.domainId.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")
33584
33791
  },
33585
33792
  {
33586
33793
  type: "input",
@@ -33592,7 +33799,7 @@ async function runDomainsAdd(options = {}) {
33592
33799
  type: "input",
33593
33800
  name: "ownedPathsRaw",
33594
33801
  message: "Owned path globs (comma-separated):",
33595
- default: (answers) => answers.domainPath ? `${answers.domainPath}/**` : "",
33802
+ default: (answers2) => answers2.domainPath ? `${answers2.domainPath}/**` : "",
33596
33803
  validate: (v) => v.trim().length > 0 || "At least one owned path is required"
33597
33804
  }
33598
33805
  ]);
@@ -41209,12 +41416,12 @@ var ApprovalRegistry = class {
41209
41416
  `Approval request "${id}" is ${current2.status}, not approved; only an approved request can be completed.`
41210
41417
  );
41211
41418
  }
41212
- const completed = { ...current2, status: "completed" };
41419
+ const completed2 = { ...current2, status: "completed" };
41213
41420
  const next = [...requests];
41214
- next[idx] = completed;
41421
+ next[idx] = completed2;
41215
41422
  persistRequests(this.dataDir, next);
41216
41423
  this.store.replaceAll(next);
41217
- return completed;
41424
+ return completed2;
41218
41425
  }
41219
41426
  /**
41220
41427
  * Flip every pending request whose expiresAt is at or before the supplied
@@ -45509,6 +45716,21 @@ function adminPlaceProject(cfg, sessionId, projectId, unitId) {
45509
45716
  function adminRemoveUnit(cfg, sessionId, unitId, disposition) {
45510
45717
  removeUnit(cfg, sessionId, unitId, disposition);
45511
45718
  }
45719
+ function adminGetLandscape(cfg, sessionId) {
45720
+ return generateLandscape(cfg, sessionId);
45721
+ }
45722
+ function adminGetHealth(cfg, sessionId) {
45723
+ return getHealthReport(cfg, sessionId);
45724
+ }
45725
+ function adminGetUsage(cfg, sessionId) {
45726
+ return getUsage(cfg, sessionId);
45727
+ }
45728
+ function adminListApprovals(cfg, sessionId) {
45729
+ return listPendingRequests(cfg, sessionId);
45730
+ }
45731
+ function adminDecideApproval(cfg, sessionId, decision) {
45732
+ return decideRequest(cfg, sessionId, decision);
45733
+ }
45512
45734
  function bodyScope(body) {
45513
45735
  return {
45514
45736
  scopeKind: body?.scopeKind ? String(body.scopeKind) : void 0,
@@ -46100,16 +46322,16 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
46100
46322
  return sendJson(res, 200, { entries: adminGetShareAccessLog(cfg, sessionId, url.searchParams.get("linkId") ?? "", limit) });
46101
46323
  }
46102
46324
  if (req.method === "GET" && parts.length === 3 && parts[2] === "landscape") {
46103
- return sendJson(res, 200, generateLandscape(cfg, sessionId));
46325
+ return sendJson(res, 200, adminGetLandscape(cfg, sessionId));
46104
46326
  }
46105
46327
  if (req.method === "GET" && parts.length === 3 && parts[2] === "health") {
46106
- return sendJson(res, 200, getHealthReport(cfg, sessionId));
46328
+ return sendJson(res, 200, adminGetHealth(cfg, sessionId));
46107
46329
  }
46108
46330
  if (req.method === "GET" && parts.length === 3 && parts[2] === "usage") {
46109
- return sendJson(res, 200, { usage: getUsage(cfg, sessionId) });
46331
+ return sendJson(res, 200, { usage: adminGetUsage(cfg, sessionId) });
46110
46332
  }
46111
46333
  if (req.method === "GET" && parts.length === 3 && parts[2] === "approvals") {
46112
- return sendJson(res, 200, { requests: listPendingRequests(cfg, sessionId) });
46334
+ return sendJson(res, 200, { requests: adminListApprovals(cfg, sessionId) });
46113
46335
  }
46114
46336
  if (req.method === "POST" && parts.length === 4 && parts[2] === "approvals" && parts[3] === "decide") {
46115
46337
  const decision = {
@@ -46121,7 +46343,7 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
46121
46343
  decidedAt: ""
46122
46344
  // set server-side
46123
46345
  };
46124
- return sendJson(res, 200, decideRequest(cfg, sessionId, decision));
46346
+ return sendJson(res, 200, adminDecideApproval(cfg, sessionId, decision));
46125
46347
  }
46126
46348
  }
46127
46349
  sendJson(res, 404, { error: "not found" });
@@ -47022,6 +47244,7 @@ var WEB_MUTATION_PATHS = /* @__PURE__ */ new Set([
47022
47244
  "/web/admin/share/refresh",
47023
47245
  "/web/admin/share/update",
47024
47246
  "/web/admin/share/remove",
47247
+ "/web/admin/approvals/decide",
47025
47248
  "/web/projects/packs",
47026
47249
  "/web/projects/packs/upload",
47027
47250
  "/web/projects/packs/remove",