@wairon/cli 5.0.2-dev.5 → 5.0.2-dev.7

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.0.2-dev.5";
68
+ WAIRON_VERSION = "5.0.2-dev.7";
69
69
  GITHUB_REPO = "SYW-Apps/Waffle-AIron";
70
70
  ARCHITECT_AGENT_ID = "agent-architect";
71
71
  ARCHITECT_TEMPLATE_ID = "architect";
@@ -703,7 +703,7 @@ var init_template = __esm({
703
703
  });
704
704
 
705
705
  // src/models/specs.ts
706
- var import_zod6, 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, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, MethodSignatureSchema, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, GroupSpecSchema;
706
+ var import_zod6, 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, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, MethodSignatureSchema, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, GroupSpecSchema;
707
707
  var init_specs = __esm({
708
708
  "src/models/specs.ts"() {
709
709
  "use strict";
@@ -907,6 +907,13 @@ var init_specs = __esm({
907
907
  event: import_zod6.z.string().optional(),
908
908
  description: import_zod6.z.string().optional()
909
909
  });
910
+ ExternalLinkTypeSchema = import_zod6.z.enum(["implementation", "informative"]);
911
+ ExternalLinkSchema = import_zod6.z.object({
912
+ url: import_zod6.z.string(),
913
+ /** Defaults to 'informative' so an untyped link never silently satisfies the source requirement. */
914
+ type: ExternalLinkTypeSchema.default("informative"),
915
+ label: import_zod6.z.string().optional()
916
+ });
910
917
  ComponentSpecSchema = import_zod6.z.object({
911
918
  id: SpecIdSchema,
912
919
  name: import_zod6.z.string(),
@@ -932,6 +939,11 @@ var init_specs = __esm({
932
939
  patterns: import_zod6.z.array(PatternRefSchema).optional(),
933
940
  /** Optional component variant — a declared, base-anchored specialization of this component's stereotype (resolved against the variant registry; UNKNOWN_VARIANT / VARIANT_BASE_MISMATCH). */
934
941
  variant: import_zod6.z.string().optional(),
942
+ /** Opaque external references (see ExternalLinkSchema) — documented URLs wairon does
943
+ * not fetch or validate. An `implementation` link is the external source-of-record and
944
+ * satisfies the source requirement for a source-less implementation (suppresses
945
+ * MISSING_SOURCE_PATH); `informative` links are context only. */
946
+ externalLinks: import_zod6.z.array(ExternalLinkSchema).optional(),
935
947
  /** Per-spec lint suppressions (see LintConfigSchema). */
936
948
  lint: LintConfigSchema.optional(),
937
949
  /** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
@@ -2652,6 +2664,7 @@ function buildCanvasModel(issues = []) {
2652
2664
  ...ownerOf.has(comp.id) ? { owner: ownerOf.get(comp.id) } : {},
2653
2665
  owns: comp.owns.filter((o) => componentIds.has(o)),
2654
2666
  dependsOn: comp.dependsOn,
2667
+ ...comp.externalLinks && comp.externalLinks.length ? { externalLinks: comp.externalLinks } : {},
2655
2668
  interfaces: compInterfaces.map((i) => ({
2656
2669
  id: i.id,
2657
2670
  name: i.name,
@@ -6072,6 +6085,14 @@ var MODEL = __MODEL_JSON__;
6072
6085
  if (!exposes || !openApiAllowed()) return '';
6073
6086
  return '<div class="openbtn"><button class="tbtn" data-openapi-tag="' + esc(tag || '') + '">\\u25A4 View OpenAPI \\u2197</button></div>';
6074
6087
  }
6088
+ // "Open in Specs" \u2014 deep-links the focused spec into the hosted Specs value
6089
+ // editor via a host hook (opts.onOpenSpec). Hidden when no host provides it
6090
+ // (standalone file, shared page): those have no editor to open. kind is the
6091
+ // spec layer, id its qualified spec id.
6092
+ function openSpecButton(kind, id) {
6093
+ if (typeof opts === 'undefined' || !opts || typeof opts.onOpenSpec !== 'function') return '';
6094
+ return '<div class="openbtn"><button class="tbtn" data-openspec-kind="' + kind + '" data-openspec-id="' + esc(id) + '">\\u270E Open in Specs \\u2197</button></div>';
6095
+ }
6075
6096
 
6076
6097
  function renderPanel() {
6077
6098
  var head = '', body = '';
@@ -6094,7 +6115,8 @@ var MODEL = __MODEL_JSON__;
6094
6115
  + (scopeFocus ? staticChip('current view') : '')
6095
6116
  + chip(c.subsystem, 'subsystem', c.subsystem)
6096
6117
  + (scopeFocus ? '' : openViewButton('component', c.id, c.owns.length > 0))
6097
- + openApiButton(componentExposesApi(c), c.apiTag);
6118
+ + openApiButton(componentExposesApi(c), c.apiTag)
6119
+ + openSpecButton('component', c.id);
6098
6120
 
6099
6121
  var linkedTypes = MODEL.types.filter(function (t) { return t.componentClass === c.id; });
6100
6122
  if (linkedTypes.length) {
@@ -6104,6 +6126,14 @@ var MODEL = __MODEL_JSON__;
6104
6126
  }
6105
6127
  body += '<p class="desc">' + esc(c.description) + '</p>';
6106
6128
 
6129
+ if (c.externalLinks && c.externalLinks.length) {
6130
+ body += section('External links', c.externalLinks.length, c.externalLinks.map(function (l) {
6131
+ var label = l.label || l.url;
6132
+ var tag = l.type === 'implementation' ? staticChip('source') : '';
6133
+ return '<div class="method">' + tag + '<a href="' + esc(l.url) + '" target="_blank" rel="noopener" style="color:var(--accent);word-break:break-all">' + esc(label) + ' \\u2197</a></div>';
6134
+ }).join(''), true);
6135
+ }
6136
+
6107
6137
  var depInner = (c.dependsOn.length ? c.dependsOn.map(function (d) { return chip(d, 'component', d); }).join('') : '<span class="desc">none</span>')
6108
6138
  + (c.owns.length ? '<div style="margin-top:8px"><b style="font-size:11px">Owns:</b><br>' + c.owns.map(function (d) { return chip(d, 'component', d); }).join('') + '</div>' : '');
6109
6139
  body += section('Dependencies', c.dependsOn.length + c.owns.length, depInner, true);
@@ -6178,7 +6208,8 @@ var MODEL = __MODEL_JSON__;
6178
6208
  MODEL.types.forEach(function (t2) { if (t2.id === focusId) ty = t2; });
6179
6209
  if (ty) {
6180
6210
  head = '<h2>' + esc(ty.name) + '</h2>' + staticChip('\\u00AB' + ty.kind + '\\u00BB')
6181
- + (ty.subsystem ? chip(ty.subsystem, 'subsystem', ty.subsystem) : staticChip('system-level shared'));
6211
+ + (ty.subsystem ? chip(ty.subsystem, 'subsystem', ty.subsystem) : staticChip('system-level shared'))
6212
+ + openSpecButton('type', ty.id);
6182
6213
 
6183
6214
  if (ty.componentClass) {
6184
6215
  head += '<div style="margin-top:6px"><b style="font-size:11px">Class Component:</b> ' + chip(ty.componentClass, 'component', ty.componentClass) + '</div>';
@@ -6254,7 +6285,8 @@ var MODEL = __MODEL_JSON__;
6254
6285
  + (s.status ? staticChip(s.status) : '')
6255
6286
  + (scopeFocus ? staticChip('current view') : '')
6256
6287
  + (scopeFocus ? '' : openViewButton('subsystem', s.id, subKids > 0))
6257
- + openApiButton(subsystemExposesApi(s.id), '');
6288
+ + openApiButton(subsystemExposesApi(s.id), '')
6289
+ + openSpecButton('subsystem', s.id);
6258
6290
  body += '<p class="desc">' + esc(s.description) + '</p>';
6259
6291
  if (s.trustedLinks.length) {
6260
6292
  body += section('Trusted links (fast lanes)', s.trustedLinks.length, s.trustedLinks.map(function (t2) {
@@ -6312,6 +6344,16 @@ var MODEL = __MODEL_JSON__;
6312
6344
  });
6313
6345
  })(oapis[oi]);
6314
6346
  }
6347
+ var ospecs = panel.querySelectorAll('[data-openspec-kind]');
6348
+ for (var si = 0; si < ospecs.length; si++) {
6349
+ (function (b) {
6350
+ b.addEventListener('click', function () {
6351
+ if (typeof opts !== 'undefined' && opts && typeof opts.onOpenSpec === 'function') {
6352
+ opts.onOpenSpec(b.getAttribute('data-openspec-kind'), b.getAttribute('data-openspec-id') || '');
6353
+ }
6354
+ });
6355
+ })(ospecs[si]);
6356
+ }
6315
6357
  var flows = panel.querySelectorAll('[data-flow-comp]');
6316
6358
  for (var j = 0; j < flows.length; j++) {
6317
6359
  (function (b) {
@@ -6324,6 +6366,23 @@ var MODEL = __MODEL_JSON__;
6324
6366
  }
6325
6367
 
6326
6368
  renderPanel();
6369
+ // Stage G: a deep link may focus a component and/or open a method's narrative
6370
+ // modal once the seeded view + DOM + cy graph exist. The host parses the URL hash
6371
+ // into opts.initialSelect / opts.initialFlow \u2014 both carry the component id, so this
6372
+ // works whether the seeded view is the component itself or its parent subsystem
6373
+ // (a leaf component has no meaningful "inside", so Specs deep-links open the parent
6374
+ // and focus the component here).
6375
+ (function () {
6376
+ if (typeof opts === 'undefined' || !opts) return;
6377
+ var f = opts.initialFlow, s = opts.initialSelect;
6378
+ var focusComp = (f && f.comp) || (s && s.comp);
6379
+ if (focusComp && compById[focusComp]) {
6380
+ try { select('component', focusComp, true); } catch (e) { /* ignore */ }
6381
+ }
6382
+ if (f && f.comp && f.method && compById[f.comp]) {
6383
+ try { openFlow(f.comp, f.method, f.mode === 'steps' ? 'steps' : 'flow'); } catch (e) { /* ignore */ }
6384
+ }
6385
+ })();
6327
6386
  })();
6328
6387
  </script>
6329
6388
  </body>
@@ -8436,13 +8495,16 @@ var init_conformance = __esm({
8436
8495
  if (isInChainedSubproject(component.subsystem, ctx)) continue;
8437
8496
  const draft = ctx.isImplementationDraft(impl);
8438
8497
  if (!impl.sourcePath) {
8439
- ctx.addIssue(
8440
- "warning",
8441
- "MISSING_SOURCE_PATH",
8442
- `Implementation "${impl.id}" declares no sourcePath \u2014 its contract "${impl.contract}" cannot be structurally checked against code.`,
8443
- impl.id,
8444
- draft
8445
- );
8498
+ const hasExternalSource = (component.externalLinks ?? []).some((l) => l.type === "implementation");
8499
+ if (!hasExternalSource) {
8500
+ ctx.addIssue(
8501
+ "warning",
8502
+ "MISSING_SOURCE_PATH",
8503
+ `Implementation "${impl.id}" declares no sourcePath \u2014 its contract "${impl.contract}" cannot be structurally checked against code.`,
8504
+ impl.id,
8505
+ draft
8506
+ );
8507
+ }
8446
8508
  continue;
8447
8509
  }
8448
8510
  const lookup = lookups.get(normalizeSourcePath(impl.sourcePath));
@@ -12705,6 +12767,28 @@ function buildGraphModel(level) {
12705
12767
  ...t.subsystem ? { parentId: t.subsystem } : {}
12706
12768
  });
12707
12769
  }
12770
+ const componentByInterface = /* @__PURE__ */ new Map();
12771
+ for (const c of model.components) {
12772
+ for (const intf of c.interfaces) componentByInterface.set(intf.id, c.id);
12773
+ }
12774
+ for (const impl of loadImplementationSpecs()) {
12775
+ const componentId = componentByInterface.get(impl.contract);
12776
+ if (!componentId) continue;
12777
+ nodes.push({
12778
+ id: impl.id,
12779
+ label: impl.name,
12780
+ kind: "implementation",
12781
+ level: 4,
12782
+ parentId: componentId,
12783
+ ...impl.status ? { status: impl.status } : {}
12784
+ });
12785
+ candidates.push({ from: componentId, to: impl.id, edgeKind: "owns" });
12786
+ }
12787
+ for (const c of model.components) {
12788
+ for (const memberId of c.owns) {
12789
+ candidates.push({ from: c.id, to: memberId, edgeKind: "owns" });
12790
+ }
12791
+ }
12708
12792
  for (const e of model.edges) {
12709
12793
  candidates.push({ from: e.from, to: e.to, edgeKind: "depends_on" });
12710
12794
  }
@@ -27670,6 +27754,7 @@ function removeIdentityProviderRecord(dataDir, id) {
27670
27754
  }
27671
27755
  var PROJECT_CREATE_CAPABILITY = "project:create";
27672
27756
  var PROJECT_WRITE_CAPABILITY = "project:write";
27757
+ var PROJECT_READ_CAPABILITY = "project:read";
27673
27758
  var POLICY_MANAGE_CAPABILITY = "project:admin";
27674
27759
  function carriesInstancePermission(cfg, principal, capability) {
27675
27760
  return authorize(cfg.dataDir, principal, capability, "instance", "").value === "yes";
@@ -27734,6 +27819,19 @@ function recordProjectProfileSelection(root, selection) {
27734
27819
  writeYamlFile(AI_PATHS.projectConfig(), raw);
27735
27820
  });
27736
27821
  }
27822
+ function readProjectType(root) {
27823
+ return runWithProjectRoot(root, () => {
27824
+ const raw = readYamlFile(AI_PATHS.projectConfig());
27825
+ return raw?.projectType ?? "backend";
27826
+ });
27827
+ }
27828
+ function writeProjectType(root, projectType) {
27829
+ runWithProjectRoot(root, () => {
27830
+ const raw = readYamlFile(AI_PATHS.projectConfig()) ?? {};
27831
+ raw["projectType"] = projectType;
27832
+ writeYamlFile(AI_PATHS.projectConfig(), raw);
27833
+ });
27834
+ }
27737
27835
  function requestPackNames(request) {
27738
27836
  const sel = request.profileSelection;
27739
27837
  if (!sel) return [];
@@ -27928,6 +28026,32 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
27928
28026
  );
27929
28027
  return result;
27930
28028
  }
28029
+ function getProjectConfig(cfg, credential, projectId) {
28030
+ const principal = requirePrincipal4(cfg, credential);
28031
+ if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY, "project", projectId).value !== "yes") {
28032
+ throw new ForbiddenError(
28033
+ "reading a project's configuration requires project:read over the project"
28034
+ );
28035
+ }
28036
+ const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
28037
+ if (!root) throw new Error(`Unknown project "${projectId}".`);
28038
+ const projectType = readProjectType(root);
28039
+ const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
28040
+ return { projectType, locked };
28041
+ }
28042
+ function setProjectType(cfg, credential, projectId, projectType) {
28043
+ const principal = requirePrincipal4(cfg, credential);
28044
+ if (authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY, "project", projectId).value !== "yes") {
28045
+ throw new ForbiddenError(
28046
+ "changing a project's configuration requires project:write over the project"
28047
+ );
28048
+ }
28049
+ const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
28050
+ if (!root) throw new Error(`Unknown project "${projectId}".`);
28051
+ writeProjectType(root, projectType);
28052
+ const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
28053
+ return { projectType, locked };
28054
+ }
27931
28055
  function getPackPolicy(cfg, credential) {
27932
28056
  requirePrincipal4(cfg, credential);
27933
28057
  return getPackPolicyRecord(cfg.dataDir) ?? PERMISSIVE_DEFAULT_POLICY;
@@ -27991,7 +28115,7 @@ function handlePolicyRequest(cfg, credential, req, res, body, url) {
27991
28115
 
27992
28116
  // src/server/identity.ts
27993
28117
  var PROJECT_ADMIN_CAPABILITY2 = "project:admin";
27994
- var PROJECT_READ_CAPABILITY = "project:read";
28118
+ var PROJECT_READ_CAPABILITY2 = "project:read";
27995
28119
  var PROJECT_WRITE_CAPABILITY2 = "project:write";
27996
28120
  function requireInstanceProjectAdmin(cfg, principal, what) {
27997
28121
  if (authorize(cfg.dataDir, principal, PROJECT_ADMIN_CAPABILITY2, "instance", "").value !== "yes") {
@@ -28137,7 +28261,7 @@ function revokeToken(cfg, credential, tokenId) {
28137
28261
  }
28138
28262
  function mintSelfToken(cfg, credential, projectId, write) {
28139
28263
  const principal = requirePrincipal5(cfg, credential);
28140
- if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY, "project", projectId).value !== "yes") {
28264
+ if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2, "project", projectId).value !== "yes") {
28141
28265
  throw new ForbiddenError("caller lacks project:read on the requested project");
28142
28266
  }
28143
28267
  if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", projectId).value !== "yes") {
@@ -28783,7 +28907,7 @@ function audienceDistance(resolution, targetProjectId) {
28783
28907
  // src/server/landscape.ts
28784
28908
  var yamlLib = __toESM(require("js-yaml"));
28785
28909
  var PROJECT_ADMIN_CAPABILITY3 = "project:admin";
28786
- var PROJECT_READ_CAPABILITY2 = "project:read";
28910
+ var PROJECT_READ_CAPABILITY3 = "project:read";
28787
28911
  var PROJECT_WRITE_CAPABILITY3 = "project:write";
28788
28912
  function requirePrincipal6(cfg, credential) {
28789
28913
  const principal = authenticateCredential(cfg.dataDir, credential);
@@ -28797,7 +28921,7 @@ function readView(cfg, principal) {
28797
28921
  if (isInstanceAdmin(principal)) {
28798
28922
  return { all: true, projectIds: /* @__PURE__ */ new Set(), unitIds: /* @__PURE__ */ new Set(), contextUnitIds: /* @__PURE__ */ new Set() };
28799
28923
  }
28800
- const read = visibleScopes(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2);
28924
+ const read = visibleScopes(cfg.dataDir, principal, PROJECT_READ_CAPABILITY3);
28801
28925
  const admin = visibleScopes(cfg.dataDir, principal, PROJECT_ADMIN_CAPABILITY3);
28802
28926
  const unitIds = /* @__PURE__ */ new Set([...actionableUnitIds(read), ...actionableUnitIds(admin)]);
28803
28927
  const contextUnitIds = new Set(
@@ -28812,13 +28936,13 @@ function readView(cfg, principal) {
28812
28936
  }
28813
28937
  function requireLandscapeReach(cfg, principal, view, what) {
28814
28938
  if (view.all || view.projectIds.size > 0 || view.unitIds.size > 0) return;
28815
- if (permitsCap(cfg, principal, PROJECT_READ_CAPABILITY2, "instance", "") || permitsCap(cfg, principal, PROJECT_ADMIN_CAPABILITY3, "instance", "")) {
28939
+ if (permitsCap(cfg, principal, PROJECT_READ_CAPABILITY3, "instance", "") || permitsCap(cfg, principal, PROJECT_ADMIN_CAPABILITY3, "instance", "")) {
28816
28940
  return;
28817
28941
  }
28818
28942
  throw new ForbiddenError(`${what} requires project:read reach`);
28819
28943
  }
28820
28944
  function requireObserverScope(cfg, principal, observerProjectId) {
28821
- const covered = permitsCap(cfg, principal, PROJECT_READ_CAPABILITY2, "project", observerProjectId) || permitsCap(cfg, principal, PROJECT_WRITE_CAPABILITY3, "project", observerProjectId);
28945
+ const covered = permitsCap(cfg, principal, PROJECT_READ_CAPABILITY3, "project", observerProjectId) || permitsCap(cfg, principal, PROJECT_WRITE_CAPABILITY3, "project", observerProjectId);
28822
28946
  if (!covered) {
28823
28947
  throw new ForbiddenError("caller lacks scope over the current project");
28824
28948
  }
@@ -30876,6 +31000,12 @@ function evaluateProjectPolicy2(cfg, credential, projectId) {
30876
31000
  function reconcileProjectPolicy2(cfg, credential, projectId) {
30877
31001
  return reconcileProjectPolicy(cfg, credential, projectId);
30878
31002
  }
31003
+ function getProjectConfig2(cfg, credential, projectId) {
31004
+ return getProjectConfig(cfg, credential, projectId);
31005
+ }
31006
+ function setProjectType2(cfg, credential, projectId, projectType) {
31007
+ return setProjectType(cfg, credential, projectId, projectType);
31008
+ }
30879
31009
  function listProducers2(cfg, credential, project2) {
30880
31010
  return listProducers(cfg, credential, project2);
30881
31011
  }
@@ -34178,6 +34308,12 @@ function opsPolicyEvaluate(cfg, sessionId, url, res) {
34178
34308
  function opsPolicyReconcile(cfg, sessionId, body, res) {
34179
34309
  sendJson(res, 200, reconcileProjectPolicy2(cfg, sessionId, String(body?.projectId ?? "")));
34180
34310
  }
34311
+ function opsGetProjectConfig(cfg, sessionId, url, res) {
34312
+ sendJson(res, 200, getProjectConfig2(cfg, sessionId, q(url, "projectId") ?? ""));
34313
+ }
34314
+ function opsSetProjectConfig(cfg, sessionId, body, res) {
34315
+ sendJson(res, 200, setProjectType2(cfg, sessionId, String(body?.projectId ?? ""), String(body?.projectType ?? "")));
34316
+ }
34181
34317
  function opsListProducers(cfg, sessionId, url, res) {
34182
34318
  sendJson(res, 200, { producers: listProducers2(cfg, sessionId, q(url, "projectId") ?? "") });
34183
34319
  }
@@ -34410,6 +34546,12 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
34410
34546
  if (req.method === "POST" && parts.length === 4 && parts[2] === "packs" && parts[3] === "adopt") {
34411
34547
  return opsAdoptProjectPack(cfg, sessionId, body, res);
34412
34548
  }
34549
+ if (req.method === "GET" && parts.length === 3 && parts[2] === "config") {
34550
+ return opsGetProjectConfig(cfg, sessionId, url, res);
34551
+ }
34552
+ if (req.method === "POST" && parts.length === 3 && parts[2] === "config") {
34553
+ return opsSetProjectConfig(cfg, sessionId, body, res);
34554
+ }
34413
34555
  if (req.method === "GET" && parts.length === 3 && parts[2] === "policy") {
34414
34556
  return opsPolicyEvaluate(cfg, sessionId, url, res);
34415
34557
  }