syntaur 0.47.0 → 0.48.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 (72) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dashboard/dist/assets/{_basePickBy-DgR0_P-o.js → _basePickBy-Cie7FXcV.js} +1 -1
  3. package/dashboard/dist/assets/{_baseUniq-C8_Ych09.js → _baseUniq-C2af7-lW.js} +1 -1
  4. package/dashboard/dist/assets/{arc-yMHz4vGa.js → arc-BHKGjEv8.js} +1 -1
  5. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-ColWcH3P.js → architectureDiagram-2XIMDMQ5-CWqzcG1t.js} +1 -1
  6. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-Bo8Npvfq.js → blockDiagram-WCTKOSBZ-CHLC0M63.js} +1 -1
  7. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-B2ky8AT7.js → c4Diagram-IC4MRINW-Bx6GTZzJ.js} +1 -1
  8. package/dashboard/dist/assets/channel-CXlttLwe.js +1 -0
  9. package/dashboard/dist/assets/{chunk-4BX2VUAB-CyF6Z6dx.js → chunk-4BX2VUAB-7cTgwG6c.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-55IACEB6-BJOEnwNN.js → chunk-55IACEB6-DQmdA20e.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-FMBD7UC4-D3siQyQ4.js → chunk-FMBD7UC4-knmOnIKI.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-JSJVCQXG-DKGuxEMf.js → chunk-JSJVCQXG-CaiLHJFn.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-KX2RTZJC-CNIWWO2F.js → chunk-KX2RTZJC-Crp4zWuY.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-NQ4KR5QH-DXt05c7h.js → chunk-NQ4KR5QH-DDjtdNaK.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-QZHKN3VN-CM63uYnf.js → chunk-QZHKN3VN-Cbm1IieP.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-WL4C6EOR-Dqvl_14m.js → chunk-WL4C6EOR-CdrRh6R4.js} +1 -1
  17. package/dashboard/dist/assets/classDiagram-VBA2DB6C-BJM0Dprk.js +1 -0
  18. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BJM0Dprk.js +1 -0
  19. package/dashboard/dist/assets/clone-BTq7ueDt.js +1 -0
  20. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-WBLtT1w9.js → cose-bilkent-S5V4N54A-DyNajnMF.js} +1 -1
  21. package/dashboard/dist/assets/{dagre-KLK3FWXG-DIdQdwa7.js → dagre-KLK3FWXG-CIBjpfqE.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-E7M64L7V-BEH6P_Sk.js → diagram-E7M64L7V-CDENlpZN.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-IFDJBPK2-BuhxBcSy.js → diagram-IFDJBPK2-CIGMJoFB.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-P4PSJMXO-DPSNVVzN.js → diagram-P4PSJMXO-C27fj5wp.js} +1 -1
  25. package/dashboard/dist/assets/{erDiagram-INFDFZHY-DYJb_rF5.js → erDiagram-INFDFZHY-M2CywJym.js} +1 -1
  26. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-B9_8BI26.js → flowDiagram-PKNHOUZH-CTV6ROYf.js} +1 -1
  27. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-Bsg3QOhs.js → ganttDiagram-A5KZAMGK-CxH3f3e5.js} +1 -1
  28. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Cf5G9x_K.js → gitGraphDiagram-K3NZZRJ6-CwhnVvHE.js} +1 -1
  29. package/dashboard/dist/assets/{graph-DyXfcrIH.js → graph-DGkI_ekZ.js} +1 -1
  30. package/dashboard/dist/assets/index-BxO2I5dN.css +1 -0
  31. package/dashboard/dist/assets/{index-C3kYxhbQ.js → index-CTdFARW9.js} +111 -111
  32. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Bu1zlXs2.js → infoDiagram-LFFYTUFH-CuKReAi2.js} +1 -1
  33. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-fb8C-XRT.js → ishikawaDiagram-PHBUUO56-DyBMt5Mw.js} +1 -1
  34. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-smlBWs2O.js → journeyDiagram-4ABVD52K-CnwtbTNs.js} +1 -1
  35. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-Bz1AxFRE.js → kanban-definition-K7BYSVSG-5Sh1bC5j.js} +1 -1
  36. package/dashboard/dist/assets/{layout-VsTD3onG.js → layout-W2Vytcip.js} +1 -1
  37. package/dashboard/dist/assets/{linear-CE8xncGu.js → linear-UTm9E6c4.js} +1 -1
  38. package/dashboard/dist/assets/{mermaid.core-C0KQpDyW.js → mermaid.core-B0EB-dBk.js} +4 -4
  39. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-SRE5Immj.js → mindmap-definition-YRQLILUH-CH4L08eg.js} +1 -1
  40. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CaZ_aCcD.js → pieDiagram-SKSYHLDU-CQ3PMl-w.js} +1 -1
  41. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Dd6MIruu.js → quadrantDiagram-337W2JSQ-DbZgXUm5.js} +1 -1
  42. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BBXvP53l.js → requirementDiagram-Z7DCOOCP-CxJWytQa.js} +1 -1
  43. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DnS1SMIm.js → sankeyDiagram-WA2Y5GQK-3YeEasvh.js} +1 -1
  44. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CLHJ1Uhx.js → sequenceDiagram-2WXFIKYE-F2EyvYsW.js} +1 -1
  45. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-B6vrAeYw.js → stateDiagram-RAJIS63D-DrmiemaD.js} +1 -1
  46. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DfyvP5EF.js +1 -0
  47. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BlHwGfnL.js → timeline-definition-YZTLITO2-CBee2YiE.js} +1 -1
  48. package/dashboard/dist/assets/{treemap-KZPCXAKY-D9kOGUYR.js → treemap-KZPCXAKY-BkPRIS3K.js} +1 -1
  49. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-BpQgeveT.js → vennDiagram-LZ73GAT5-CTsfwv8G.js} +1 -1
  50. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DRch79fE.js → xychartDiagram-JWTSCODW-DUSo-c6w.js} +1 -1
  51. package/dashboard/dist/index.html +2 -2
  52. package/dist/dashboard/server.js +162 -68
  53. package/dist/dashboard/server.js.map +1 -1
  54. package/dist/index.js +162 -68
  55. package/dist/index.js.map +1 -1
  56. package/dist/launch/index.d.ts +25 -12
  57. package/dist/launch/index.js +60 -57
  58. package/dist/launch/index.js.map +1 -1
  59. package/package.json +1 -1
  60. package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
  61. package/platforms/claude-code/skills/manage-statuses/SKILL.md +7 -0
  62. package/platforms/codex/.codex-plugin/plugin.json +1 -1
  63. package/platforms/codex/skills/manage-statuses/SKILL.md +7 -0
  64. package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
  65. package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
  66. package/skills/manage-statuses/SKILL.md +7 -0
  67. package/dashboard/dist/assets/channel-CUTEvTdk.js +0 -1
  68. package/dashboard/dist/assets/classDiagram-VBA2DB6C-Bkoc7orC.js +0 -1
  69. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Bkoc7orC.js +0 -1
  70. package/dashboard/dist/assets/clone-CltBg7cH.js +0 -1
  71. package/dashboard/dist/assets/index-DKr21dk8.css +0 -1
  72. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BeqNZKbk.js +0 -1
package/dist/index.js CHANGED
@@ -3107,6 +3107,53 @@ function factFieldNames(decl) {
3107
3107
  ].map((k) => k.toLowerCase()) : [exportNames.fact.toLowerCase()];
3108
3108
  return { storageKey: name, exports: exportNames, registryKeys };
3109
3109
  }
3110
+ function validateFactDeclarations(raw2) {
3111
+ const problems = [];
3112
+ const owners = /* @__PURE__ */ new Map();
3113
+ for (const key of Object.keys(DERIVE_FIELDS)) owners.set(key, "built-in");
3114
+ for (const key of Object.keys(ASSIGNMENT_FIELDS)) owners.set(key, "built-in");
3115
+ for (const row of raw2 ?? []) {
3116
+ const name = (row?.name ?? "").trim();
3117
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(name)) {
3118
+ problems.push(
3119
+ `fact "${row?.name ?? ""}": invalid name \u2014 must match /^[a-z][a-zA-Z0-9]*$/`
3120
+ );
3121
+ continue;
3122
+ }
3123
+ const type = (row.type ?? "").trim();
3124
+ if (type !== "bool" && type !== "number" && type !== "attestation") {
3125
+ problems.push(
3126
+ `fact "${name}": invalid type "${row.type ?? ""}" \u2014 expected bool, number, or attestation`
3127
+ );
3128
+ continue;
3129
+ }
3130
+ if (type === "attestation") {
3131
+ const binds = (row.binds ?? "none").toString().trim() || "none";
3132
+ if (binds !== "plan" && binds !== "commit" && binds !== "none") {
3133
+ problems.push(
3134
+ `fact "${name}": invalid binds "${row.binds}" \u2014 expected plan, commit, or none`
3135
+ );
3136
+ continue;
3137
+ }
3138
+ }
3139
+ const decl = type === "attestation" ? { name, type, binds: "none" } : { name, type };
3140
+ const keys = factFieldNames(decl).registryKeys;
3141
+ const collidingKey = keys.find((k) => owners.has(k));
3142
+ if (collidingKey !== void 0) {
3143
+ const owner = owners.get(collidingKey);
3144
+ if (owner === "built-in") {
3145
+ problems.push(`fact "${name}": exported field "${collidingKey}" collides with a built-in field`);
3146
+ } else if (owner === name) {
3147
+ problems.push(`fact "${name}": duplicate declaration (a fact named "${name}" is already declared)`);
3148
+ } else {
3149
+ problems.push(`fact "${name}": exported field "${collidingKey}" collides with fact "${owner}"`);
3150
+ }
3151
+ continue;
3152
+ }
3153
+ for (const key of keys) owners.set(key, name);
3154
+ }
3155
+ return problems;
3156
+ }
3110
3157
  function acceptFactDeclarations(declarations) {
3111
3158
  const taken = /* @__PURE__ */ new Set([
3112
3159
  ...Object.keys(DERIVE_FIELDS),
@@ -3632,7 +3679,6 @@ function parseStatusConfig(content) {
3632
3679
  continue;
3633
3680
  }
3634
3681
  }
3635
- if (statuses.length === 0) return null;
3636
3682
  const derive = phaseLadder.length > 0 || disposition.length > 0 || Object.keys(headline).length > 0 ? {
3637
3683
  phaseLadder: phaseLadder.length > 0 ? phaseLadder : DEFAULT_DERIVE_CONFIG.phaseLadder,
3638
3684
  disposition: disposition.length > 0 ? disposition : DEFAULT_DERIVE_CONFIG.disposition,
@@ -3643,6 +3689,7 @@ function parseStatusConfig(content) {
3643
3689
  active: "phase"
3644
3690
  }
3645
3691
  } : null;
3692
+ if (statuses.length === 0 && facts.length === 0 && derive === null) return null;
3646
3693
  return {
3647
3694
  statuses,
3648
3695
  order: order.length > 0 ? order : statuses.map((s) => s.id),
@@ -3768,53 +3815,6 @@ function validateDeriveConfig(derive, statusConfig, validateWhen = () => null) {
3768
3815
  }
3769
3816
  return problems;
3770
3817
  }
3771
- function validateFactDeclarations(raw2) {
3772
- const problems = [];
3773
- const owners = /* @__PURE__ */ new Map();
3774
- for (const key of Object.keys(DERIVE_FIELDS)) owners.set(key, "built-in");
3775
- for (const key of Object.keys(ASSIGNMENT_FIELDS)) owners.set(key, "built-in");
3776
- for (const row of raw2 ?? []) {
3777
- const name = (row?.name ?? "").trim();
3778
- if (!/^[a-z][a-zA-Z0-9]*$/.test(name)) {
3779
- problems.push(
3780
- `fact "${row?.name ?? ""}": invalid name \u2014 must match /^[a-z][a-zA-Z0-9]*$/`
3781
- );
3782
- continue;
3783
- }
3784
- const type = (row.type ?? "").trim();
3785
- if (type !== "bool" && type !== "number" && type !== "attestation") {
3786
- problems.push(
3787
- `fact "${name}": invalid type "${row.type ?? ""}" \u2014 expected bool, number, or attestation`
3788
- );
3789
- continue;
3790
- }
3791
- if (type === "attestation") {
3792
- const binds = (row.binds ?? "none").toString().trim() || "none";
3793
- if (binds !== "plan" && binds !== "commit" && binds !== "none") {
3794
- problems.push(
3795
- `fact "${name}": invalid binds "${row.binds}" \u2014 expected plan, commit, or none`
3796
- );
3797
- continue;
3798
- }
3799
- }
3800
- const decl = type === "attestation" ? { name, type, binds: "none" } : { name, type };
3801
- const keys = factFieldNames(decl).registryKeys;
3802
- const collidingKey = keys.find((k) => owners.has(k));
3803
- if (collidingKey !== void 0) {
3804
- const owner = owners.get(collidingKey);
3805
- if (owner === "built-in") {
3806
- problems.push(`fact "${name}": exported field "${collidingKey}" collides with a built-in field`);
3807
- } else if (owner === name) {
3808
- problems.push(`fact "${name}": duplicate declaration (a fact named "${name}" is already declared)`);
3809
- } else {
3810
- problems.push(`fact "${name}": exported field "${collidingKey}" collides with fact "${owner}"`);
3811
- }
3812
- continue;
3813
- }
3814
- for (const key of keys) owners.set(key, name);
3815
- }
3816
- return problems;
3817
- }
3818
3818
  function serializeIntegrationConfig(integrations) {
3819
3819
  const lines = [];
3820
3820
  if (integrations.claudePluginDir) {
@@ -4894,7 +4894,6 @@ var init_config2 = __esm({
4894
4894
  init_agents_schema();
4895
4895
  init_slug();
4896
4896
  init_fact_registry();
4897
- init_query();
4898
4897
  init_terminal_schema();
4899
4898
  init_workspace_visibility_schema();
4900
4899
  DEFAULT_DERIVE_CONFIG = {
@@ -8766,24 +8765,28 @@ async function getStatusConfig() {
8766
8765
  if (_cachedConfig) return _cachedConfig;
8767
8766
  const config = await readConfig();
8768
8767
  if (config.statuses) {
8768
+ const sc = config.statuses;
8769
+ const defaults = sc.statuses.length === 0 ? buildDefaultStatusConfig() : null;
8770
+ const effectiveStatuses = defaults ? defaults.statuses : sc.statuses;
8771
+ const effectiveOrder = defaults ? defaults.order : sc.order;
8769
8772
  const terminalSet = new Set(
8770
- config.statuses.statuses.filter((s) => s.terminal).map((s) => s.id)
8773
+ effectiveStatuses.filter((s) => s.terminal).map((s) => s.id)
8771
8774
  );
8772
- const hasCustomTransitions = config.statuses.transitions.length > 0;
8773
- const effectiveTransitions2 = hasCustomTransitions ? config.statuses.transitions : Array.from(DEFAULT_TRANSITION_TABLE.entries()).map(([key, to]) => {
8775
+ const hasCustomTransitions = sc.transitions.length > 0;
8776
+ const effectiveTransitions2 = hasCustomTransitions ? sc.transitions : Array.from(DEFAULT_TRANSITION_TABLE.entries()).map(([key, to]) => {
8774
8777
  const [from, command] = key.split(":");
8775
8778
  return { from, command, to };
8776
8779
  });
8777
- const accepted = acceptFactDeclarations(normalizeFactDeclarations(config.statuses.facts ?? null));
8780
+ const accepted = acceptFactDeclarations(normalizeFactDeclarations(sc.facts ?? null));
8778
8781
  _cachedConfig = {
8779
8782
  custom: true,
8780
- statuses: config.statuses.statuses,
8781
- order: config.statuses.order,
8783
+ statuses: effectiveStatuses,
8784
+ order: effectiveOrder,
8782
8785
  transitions: effectiveTransitions2,
8783
8786
  transitionTable: buildTransitionTable(effectiveTransitions2),
8784
8787
  terminalStatuses: terminalSet.size > 0 ? terminalSet : /* @__PURE__ */ new Set(["completed", "failed"]),
8785
- derive: config.statuses.derive ?? null,
8786
- facts: config.statuses.facts ?? null,
8788
+ derive: sc.derive ?? null,
8789
+ facts: sc.facts ?? null,
8787
8790
  factDeclarations: accepted,
8788
8791
  deriveRegistry: buildDeriveRegistry(accepted),
8789
8792
  queryRegistry: buildQueryRegistry(accepted)
@@ -9947,7 +9950,7 @@ async function buildDerivedDetail(assignment, assignmentDir, projectDir) {
9947
9950
  try {
9948
9951
  const { computeFactsDetailed: computeFactsDetailed2 } = await Promise.resolve().then(() => (init_facts(), facts_exports));
9949
9952
  const { deriveDimensions: deriveDimensions2 } = await Promise.resolve().then(() => (init_derive(), derive_exports));
9950
- const { DEFAULT_DERIVE_CONFIG: DEFAULT_DERIVE_CONFIG2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
9953
+ const { DEFAULT_DERIVE_CONFIG: DEFAULT_DERIVE_CONFIG3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
9951
9954
  const { facts, attestations } = await computeFactsDetailed2({
9952
9955
  assignmentDir,
9953
9956
  frontmatter: {
@@ -9962,7 +9965,7 @@ async function buildDerivedDetail(assignment, assignmentDir, projectDir) {
9962
9965
  });
9963
9966
  const dims = deriveDimensions2({
9964
9967
  facts,
9965
- derive: config.derive ?? DEFAULT_DERIVE_CONFIG2,
9968
+ derive: config.derive ?? DEFAULT_DERIVE_CONFIG3,
9966
9969
  currentStatus: assignment.status,
9967
9970
  terminalStatuses: config.terminalStatuses,
9968
9971
  knownStatusIds: new Set(config.statuses.map((s) => s.id)),
@@ -19380,7 +19383,9 @@ function createWorkspaceVisibilityConfigRouter() {
19380
19383
 
19381
19384
  // src/dashboard/api-status-config.ts
19382
19385
  init_config2();
19386
+ init_fact_registry();
19383
19387
  init_api();
19388
+ init_derive();
19384
19389
  import { Router as Router9 } from "express";
19385
19390
 
19386
19391
  // src/utils/status-config-resolution.ts
@@ -19788,7 +19793,8 @@ function createStatusConfigRouter(projectsDir2, assignmentsDir2) {
19788
19793
  order: config.order,
19789
19794
  transitions: config.transitions,
19790
19795
  custom: config.custom,
19791
- factDeclarations: config.factDeclarations
19796
+ factDeclarations: config.factDeclarations,
19797
+ rawFacts: config.facts ?? []
19792
19798
  });
19793
19799
  } catch (error) {
19794
19800
  console.error("Error getting status config:", error);
@@ -19812,8 +19818,27 @@ function createStatusConfigRouter(projectsDir2, assignmentsDir2) {
19812
19818
  });
19813
19819
  router.post("/", async (req, res) => {
19814
19820
  try {
19815
- const { statuses, order, transitions, resolutions: rawResolutions } = req.body ?? {};
19816
- if (!Array.isArray(statuses) || !Array.isArray(order) || !Array.isArray(transitions)) {
19821
+ const {
19822
+ statuses,
19823
+ order,
19824
+ transitions,
19825
+ facts: bodyFacts,
19826
+ factRemovalAcks,
19827
+ resolutions: rawResolutions
19828
+ } = req.body ?? {};
19829
+ const currentConfig = await getStatusConfig();
19830
+ const hasFacts = bodyFacts !== void 0;
19831
+ const hasStatuses = statuses !== void 0;
19832
+ const hasOrder = order !== void 0;
19833
+ const hasTransitions = transitions !== void 0;
19834
+ let effectiveStatuses = statuses;
19835
+ let effectiveOrder = order;
19836
+ let effectiveTransitions2 = transitions;
19837
+ if (hasFacts && !hasStatuses && !hasOrder && !hasTransitions) {
19838
+ effectiveStatuses = currentConfig.statuses;
19839
+ effectiveOrder = currentConfig.order;
19840
+ effectiveTransitions2 = currentConfig.transitions;
19841
+ } else if (!Array.isArray(effectiveStatuses) || !Array.isArray(effectiveOrder) || !Array.isArray(effectiveTransitions2)) {
19817
19842
  res.status(400).json({ error: "malformed-statuses", message: "Request body must include statuses, order, and transitions arrays" });
19818
19843
  return;
19819
19844
  }
@@ -19827,10 +19852,9 @@ function createStatusConfigRouter(projectsDir2, assignmentsDir2) {
19827
19852
  return;
19828
19853
  }
19829
19854
  const resolutions = parsed.resolutions;
19830
- const currentConfig = await getStatusConfig();
19831
19855
  const oldIds = new Set(currentConfig.statuses.map((s) => s.id));
19832
19856
  const newIds = /* @__PURE__ */ new Set();
19833
- for (const s of statuses) {
19857
+ for (const s of effectiveStatuses) {
19834
19858
  if (s && typeof s === "object" && isString(s.id)) {
19835
19859
  newIds.add(s.id);
19836
19860
  }
@@ -19911,13 +19935,83 @@ function createStatusConfigRouter(projectsDir2, assignmentsDir2) {
19911
19935
  }
19912
19936
  throw err2;
19913
19937
  }
19938
+ let factsToWrite = currentConfig.facts ?? null;
19939
+ if (hasFacts) {
19940
+ const shapedFacts = [];
19941
+ if (!Array.isArray(bodyFacts)) {
19942
+ res.status(400).json({ error: "malformed-facts", message: "facts must be an array" });
19943
+ return;
19944
+ }
19945
+ for (let i = 0; i < bodyFacts.length; i++) {
19946
+ const row = bodyFacts[i];
19947
+ if (!row || typeof row !== "object") {
19948
+ res.status(400).json({ error: "malformed-facts", message: `facts[${i}] must be an object` });
19949
+ return;
19950
+ }
19951
+ const name = row.name;
19952
+ const type = row.type;
19953
+ if (typeof name !== "string" || typeof type !== "string") {
19954
+ res.status(400).json({ error: "malformed-facts", message: `facts[${i}] must have name and type strings` });
19955
+ return;
19956
+ }
19957
+ const binds = row.binds;
19958
+ const normalizedBinds = binds === void 0 ? null : binds === null ? null : typeof binds === "string" ? binds : null;
19959
+ shapedFacts.push({ name, type, binds: normalizedBinds });
19960
+ }
19961
+ const problems = validateFactDeclarations(shapedFacts);
19962
+ if (problems.length > 0) {
19963
+ res.status(400).json({ error: "invalid-facts", problems });
19964
+ return;
19965
+ }
19966
+ const currentNames = new Set((currentConfig.facts ?? []).map((f) => f.name));
19967
+ const incomingNames = new Set(shapedFacts.map((f) => f.name));
19968
+ const removedNames = [];
19969
+ for (const name of currentNames) {
19970
+ if (!incomingNames.has(name)) removedNames.push(name);
19971
+ }
19972
+ const acks = new Set(Array.isArray(factRemovalAcks) ? factRemovalAcks.map((x) => String(x)) : []);
19973
+ const unresolvedRefs = [];
19974
+ const deriveConfig = currentConfig.derive ?? null;
19975
+ if (deriveConfig !== null && removedNames.length > 0) {
19976
+ const acceptedAll = acceptFactDeclarations(normalizeFactDeclarations(currentConfig.facts));
19977
+ const fullRegistry = buildDeriveRegistry(acceptedAll);
19978
+ for (const removedName of removedNames) {
19979
+ if (acks.has(removedName)) continue;
19980
+ const acceptedWithout = acceptedAll.filter((d) => d.name !== removedName);
19981
+ const withoutRegistry = buildDeriveRegistry(acceptedWithout);
19982
+ for (let i = 0; i < deriveConfig.phaseLadder.length; i++) {
19983
+ const rung = deriveConfig.phaseLadder[i];
19984
+ if (rung.when === "*") continue;
19985
+ const before = validateDeriveCondition(rung.when, fullRegistry);
19986
+ const after = validateDeriveCondition(rung.when, withoutRegistry);
19987
+ if (before === null && after !== null) {
19988
+ unresolvedRefs.push({ factName: removedName, location: `phaseLadder[${i}]`, when: rung.when });
19989
+ }
19990
+ }
19991
+ for (let i = 0; i < deriveConfig.disposition.length; i++) {
19992
+ const rule = deriveConfig.disposition[i];
19993
+ if (rule.when === null) continue;
19994
+ const before = validateDeriveCondition(rule.when, fullRegistry);
19995
+ const after = validateDeriveCondition(rule.when, withoutRegistry);
19996
+ if (before === null && after !== null) {
19997
+ unresolvedRefs.push({ factName: removedName, location: `disposition[${i}]`, when: rule.when });
19998
+ }
19999
+ }
20000
+ }
20001
+ }
20002
+ if (unresolvedRefs.length > 0) {
20003
+ res.status(409).json({ error: "unresolved-fact-references", references: unresolvedRefs });
20004
+ return;
20005
+ }
20006
+ factsToWrite = shapedFacts;
20007
+ }
19914
20008
  try {
19915
20009
  await writeStatusConfig({
19916
- statuses,
19917
- order,
19918
- transitions,
20010
+ statuses: effectiveStatuses,
20011
+ order: effectiveOrder,
20012
+ transitions: effectiveTransitions2,
19919
20013
  derive: currentConfig.derive ?? null,
19920
- facts: currentConfig.facts ?? null
20014
+ facts: factsToWrite
19921
20015
  });
19922
20016
  } catch (err2) {
19923
20017
  console.error("Error saving status config after applying resolutions:", err2);