@duckcodeailabs/dql-agent 1.14.0 → 1.14.1

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 (146) hide show
  1. package/dist/agent-run-engine.d.ts +60 -2
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +333 -21
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/agent-run-gates.d.ts.map +1 -1
  6. package/dist/agent-run-gates.js +13 -0
  7. package/dist/agent-run-gates.js.map +1 -1
  8. package/dist/agentic/analyst-loop.d.ts +91 -0
  9. package/dist/agentic/analyst-loop.d.ts.map +1 -0
  10. package/dist/agentic/analyst-loop.js +311 -0
  11. package/dist/agentic/analyst-loop.js.map +1 -0
  12. package/dist/agentic/assumptions.d.ts +62 -0
  13. package/dist/agentic/assumptions.d.ts.map +1 -0
  14. package/dist/agentic/assumptions.js +73 -0
  15. package/dist/agentic/assumptions.js.map +1 -0
  16. package/dist/agentic/business-explanation.d.ts +56 -0
  17. package/dist/agentic/business-explanation.d.ts.map +1 -0
  18. package/dist/agentic/business-explanation.js +130 -0
  19. package/dist/agentic/business-explanation.js.map +1 -0
  20. package/dist/agentic/identifier-ledger.d.ts +74 -0
  21. package/dist/agentic/identifier-ledger.d.ts.map +1 -0
  22. package/dist/agentic/identifier-ledger.js +151 -0
  23. package/dist/agentic/identifier-ledger.js.map +1 -0
  24. package/dist/agentic/ledger-tools.d.ts +68 -0
  25. package/dist/agentic/ledger-tools.d.ts.map +1 -0
  26. package/dist/agentic/ledger-tools.js +198 -0
  27. package/dist/agentic/ledger-tools.js.map +1 -0
  28. package/dist/agentic/orchestrator-policy.d.ts +60 -0
  29. package/dist/agentic/orchestrator-policy.d.ts.map +1 -0
  30. package/dist/agentic/orchestrator-policy.js +73 -0
  31. package/dist/agentic/orchestrator-policy.js.map +1 -0
  32. package/dist/agentic/orchestrator.d.ts +54 -0
  33. package/dist/agentic/orchestrator.d.ts.map +1 -0
  34. package/dist/agentic/orchestrator.js +67 -0
  35. package/dist/agentic/orchestrator.js.map +1 -0
  36. package/dist/agentic/preview-tool.d.ts +36 -0
  37. package/dist/agentic/preview-tool.d.ts.map +1 -0
  38. package/dist/agentic/preview-tool.js +71 -0
  39. package/dist/agentic/preview-tool.js.map +1 -0
  40. package/dist/agentic/rerank.d.ts +50 -0
  41. package/dist/agentic/rerank.d.ts.map +1 -0
  42. package/dist/agentic/rerank.js +140 -0
  43. package/dist/agentic/rerank.js.map +1 -0
  44. package/dist/agentic/research/hypothesis.d.ts +110 -0
  45. package/dist/agentic/research/hypothesis.d.ts.map +1 -0
  46. package/dist/agentic/research/hypothesis.js +125 -0
  47. package/dist/agentic/research/hypothesis.js.map +1 -0
  48. package/dist/agentic/research/synthesis.d.ts +32 -0
  49. package/dist/agentic/research/synthesis.d.ts.map +1 -0
  50. package/dist/agentic/research/synthesis.js +94 -0
  51. package/dist/agentic/research/synthesis.js.map +1 -0
  52. package/dist/agentic/research-agent.d.ts +54 -0
  53. package/dist/agentic/research-agent.d.ts.map +1 -0
  54. package/dist/agentic/research-agent.js +176 -0
  55. package/dist/agentic/research-agent.js.map +1 -0
  56. package/dist/agentic/sql-authorization.d.ts +131 -0
  57. package/dist/agentic/sql-authorization.d.ts.map +1 -0
  58. package/dist/agentic/sql-authorization.js +232 -0
  59. package/dist/agentic/sql-authorization.js.map +1 -0
  60. package/dist/agentic/tool-loop.d.ts +29 -0
  61. package/dist/agentic/tool-loop.d.ts.map +1 -1
  62. package/dist/agentic/tool-loop.js +129 -52
  63. package/dist/agentic/tool-loop.js.map +1 -1
  64. package/dist/agentic/toolset.d.ts.map +1 -1
  65. package/dist/agentic/toolset.js +155 -0
  66. package/dist/agentic/toolset.js.map +1 -1
  67. package/dist/agentic/turn-plan.d.ts +35 -0
  68. package/dist/agentic/turn-plan.d.ts.map +1 -0
  69. package/dist/agentic/turn-plan.js +108 -0
  70. package/dist/agentic/turn-plan.js.map +1 -0
  71. package/dist/agentic/value-probe.d.ts +89 -0
  72. package/dist/agentic/value-probe.d.ts.map +1 -0
  73. package/dist/agentic/value-probe.js +304 -0
  74. package/dist/agentic/value-probe.js.map +1 -0
  75. package/dist/analytical-orchestration.d.ts +80 -0
  76. package/dist/analytical-orchestration.d.ts.map +1 -1
  77. package/dist/analytical-orchestration.js +218 -6
  78. package/dist/analytical-orchestration.js.map +1 -1
  79. package/dist/analytical-request-policy.d.ts +26 -0
  80. package/dist/analytical-request-policy.d.ts.map +1 -0
  81. package/dist/analytical-request-policy.js +82 -0
  82. package/dist/analytical-request-policy.js.map +1 -0
  83. package/dist/analytical-result-facts.d.ts +13 -0
  84. package/dist/analytical-result-facts.d.ts.map +1 -1
  85. package/dist/analytical-result-facts.js +37 -1
  86. package/dist/analytical-result-facts.js.map +1 -1
  87. package/dist/answer-loop.d.ts +57 -16
  88. package/dist/answer-loop.d.ts.map +1 -1
  89. package/dist/answer-loop.js +161 -30
  90. package/dist/answer-loop.js.map +1 -1
  91. package/dist/cascade/budgets.d.ts.map +1 -1
  92. package/dist/cascade/budgets.js +8 -1
  93. package/dist/cascade/budgets.js.map +1 -1
  94. package/dist/cascade/packer.d.ts +88 -0
  95. package/dist/cascade/packer.d.ts.map +1 -0
  96. package/dist/cascade/packer.js +106 -0
  97. package/dist/cascade/packer.js.map +1 -0
  98. package/dist/conversation/result-ops.d.ts +18 -3
  99. package/dist/conversation/result-ops.d.ts.map +1 -1
  100. package/dist/conversation/result-ops.js +77 -5
  101. package/dist/conversation/result-ops.js.map +1 -1
  102. package/dist/conversation/rolling-summary.d.ts.map +1 -1
  103. package/dist/conversation/rolling-summary.js +7 -2
  104. package/dist/conversation/rolling-summary.js.map +1 -1
  105. package/dist/conversation/session-store.d.ts +3 -0
  106. package/dist/conversation/session-store.d.ts.map +1 -1
  107. package/dist/conversation/session-store.js +8 -4
  108. package/dist/conversation/session-store.js.map +1 -1
  109. package/dist/conversation/snapshot.d.ts.map +1 -1
  110. package/dist/conversation/snapshot.js +10 -1
  111. package/dist/conversation/snapshot.js.map +1 -1
  112. package/dist/hints/store.d.ts.map +1 -1
  113. package/dist/hints/store.js +15 -5
  114. package/dist/hints/store.js.map +1 -1
  115. package/dist/index.d.ts +27 -7
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +28 -5
  118. package/dist/index.js.map +1 -1
  119. package/dist/intent-controller.d.ts +8 -0
  120. package/dist/intent-controller.d.ts.map +1 -1
  121. package/dist/intent-controller.js +65 -0
  122. package/dist/intent-controller.js.map +1 -1
  123. package/dist/metadata/block-fit.d.ts +18 -0
  124. package/dist/metadata/block-fit.d.ts.map +1 -1
  125. package/dist/metadata/block-fit.js +41 -1
  126. package/dist/metadata/block-fit.js.map +1 -1
  127. package/dist/metadata/catalog.d.ts +20 -23
  128. package/dist/metadata/catalog.d.ts.map +1 -1
  129. package/dist/metadata/catalog.js +136 -11
  130. package/dist/metadata/catalog.js.map +1 -1
  131. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  132. package/dist/metadata/meaning-evidence.js +15 -7
  133. package/dist/metadata/meaning-evidence.js.map +1 -1
  134. package/dist/metadata/metric-match.d.ts +32 -0
  135. package/dist/metadata/metric-match.d.ts.map +1 -1
  136. package/dist/metadata/metric-match.js +120 -9
  137. package/dist/metadata/metric-match.js.map +1 -1
  138. package/dist/research-loop.d.ts +33 -2
  139. package/dist/research-loop.d.ts.map +1 -1
  140. package/dist/research-loop.js +44 -4
  141. package/dist/research-loop.js.map +1 -1
  142. package/dist/router.d.ts +28 -16
  143. package/dist/router.d.ts.map +1 -1
  144. package/dist/router.js +250 -17
  145. package/dist/router.js.map +1 -1
  146. package/package.json +4 -4
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Answering "what does this mean?" from governed metadata, without SQL.
3
+ *
4
+ * A definitional question — "what does the top_customers block measure?", "how
5
+ * is revenue defined here?" — is routed to `converse` today, which replies
6
+ * conversationally with NO governed evidence attached. So the product knows the
7
+ * answer (it has the description, the owner, the grain, the outputs, the
8
+ * certification status) and does not use it, which is the shape of the
9
+ * complaint that Ask AI cannot explain business context.
10
+ *
11
+ * This composes the explanation deterministically from what the catalog already
12
+ * holds. No provider, no warehouse: the facts are all in the object, and a model
13
+ * paraphrasing them can only add drift.
14
+ */
15
+ const CERTIFIED_STATUSES = new Set(['certified', 'approved']);
16
+ function stringField(payload, key) {
17
+ const value = payload?.[key];
18
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
19
+ }
20
+ function stringList(payload, key) {
21
+ const value = payload?.[key];
22
+ if (!Array.isArray(value))
23
+ return [];
24
+ return value
25
+ .map((entry) => {
26
+ if (typeof entry === 'string')
27
+ return entry;
28
+ if (entry && typeof entry === 'object') {
29
+ const name = entry.name;
30
+ return typeof name === 'string' ? name : '';
31
+ }
32
+ return '';
33
+ })
34
+ .filter((entry) => Boolean(entry));
35
+ }
36
+ /** How a reader should weigh this object, in plain words. */
37
+ function trustSentence(object) {
38
+ const status = object.status?.toLowerCase() ?? '';
39
+ if (CERTIFIED_STATUSES.has(status)) {
40
+ return object.owner
41
+ ? `It is certified and owned by ${object.owner}.`
42
+ : 'It is certified.';
43
+ }
44
+ if (status === 'draft')
45
+ return 'It is a draft — not certified, so treat the numbers as provisional.';
46
+ if (object.objectType.startsWith('dbt:') || object.objectType.startsWith('dbt_')) {
47
+ return 'This comes from the dbt project rather than a certified DQL block, so it describes the model, not an approved business definition.';
48
+ }
49
+ return status ? `Status: ${status}.` : 'It has no certification status recorded.';
50
+ }
51
+ /**
52
+ * Compose an explanation of one governed object.
53
+ *
54
+ * Returns `null` when there is nothing beyond a bare name to say. A confident
55
+ * paragraph assembled from an empty description is worse than falling through
56
+ * to the normal cascade: it reads like an answer and carries no information.
57
+ */
58
+ export function explainObject(object) {
59
+ const parts = [];
60
+ const label = object.fullName ?? object.name;
61
+ const description = object.description?.trim();
62
+ const guidance = stringField(object.payload, 'llmContext');
63
+ if (!description && !guidance)
64
+ return null;
65
+ parts.push(`**${label}** — ${description ?? guidance}`);
66
+ const grain = stringField(object.payload, 'grain');
67
+ if (grain)
68
+ parts.push(`Reported at ${grain} grain.`);
69
+ const outputs = stringList(object.payload, 'declaredOutputs');
70
+ const resolvedOutputs = outputs.length > 0 ? outputs : stringList(object.payload, 'outputs');
71
+ if (resolvedOutputs.length > 0)
72
+ parts.push(`Returns: ${resolvedOutputs.join(', ')}.`);
73
+ const dimensions = stringList(object.payload, 'dimensions');
74
+ if (dimensions.length > 0)
75
+ parts.push(`Can be broken down by: ${dimensions.join(', ')}.`);
76
+ // Authored guidance is the one field written FOR a reader deciding whether to
77
+ // use this, so it earns its own line when a description already led.
78
+ if (description && guidance && guidance !== description)
79
+ parts.push(`When to use it: ${guidance}`);
80
+ if (object.domain)
81
+ parts.push(`Domain: ${object.domain}.`);
82
+ parts.push(trustSentence(object));
83
+ return {
84
+ text: parts.join('\n\n'),
85
+ citations: [object.objectKey],
86
+ governed: CERTIFIED_STATUSES.has(object.status?.toLowerCase() ?? ''),
87
+ };
88
+ }
89
+ /**
90
+ * Pick the object a definitional question is about, then explain it.
91
+ *
92
+ * Matching is by NAME MENTION, not relevance rank: "what does top_customers
93
+ * measure?" is asking about a specific artifact, and answering about a merely
94
+ * similar one would be a different question confidently answered. When nothing
95
+ * is named, return null and let the cascade run — guessing the subject of a
96
+ * definition is exactly the failure this is meant to avoid.
97
+ */
98
+ export function composeBusinessExplanation(question, objects) {
99
+ const lower = question.toLowerCase();
100
+ const named = objects.filter((object) => {
101
+ for (const candidate of [object.name, object.fullName]) {
102
+ if (!candidate)
103
+ continue;
104
+ const bare = candidate.toLowerCase();
105
+ if (bare.length < 3)
106
+ continue;
107
+ if (lower.includes(bare))
108
+ return true;
109
+ const spaced = bare.replace(/[_.]+/g, ' ');
110
+ if (spaced.length > 3 && lower.includes(spaced))
111
+ return true;
112
+ }
113
+ return false;
114
+ });
115
+ if (named.length === 0)
116
+ return null;
117
+ // Prefer the most specific mention, then the most trustworthy artifact: a
118
+ // certified block explains a concept better than the raw dbt model beneath it.
119
+ const best = [...named].sort((left, right) => {
120
+ const leftName = (left.fullName ?? left.name).length;
121
+ const rightName = (right.fullName ?? right.name).length;
122
+ const leftCertified = CERTIFIED_STATUSES.has(left.status?.toLowerCase() ?? '') ? 1 : 0;
123
+ const rightCertified = CERTIFIED_STATUSES.has(right.status?.toLowerCase() ?? '') ? 1 : 0;
124
+ return rightCertified - leftCertified
125
+ || rightName - leftName
126
+ || left.objectKey.localeCompare(right.objectKey);
127
+ })[0];
128
+ return explainObject(best);
129
+ }
130
+ //# sourceMappingURL=business-explanation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"business-explanation.js","sourceRoot":"","sources":["../../src/agentic/business-explanation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA2BH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AAE9D,SAAS,WAAW,CAAC,OAA4C,EAAE,GAAW;IAC5E,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC;AAED,SAAS,UAAU,CAAC,OAA4C,EAAE,GAAW;IAC3E,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAC;YAChD,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,6DAA6D;AAC7D,SAAS,aAAa,CAAC,MAAyB;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAClD,IAAI,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC,KAAK;YACjB,CAAC,CAAC,gCAAgC,MAAM,CAAC,KAAK,GAAG;YACjD,CAAC,CAAC,kBAAkB,CAAC;IACzB,CAAC;IACD,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,qEAAqE,CAAC;IACrG,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACjF,OAAO,oIAAoI,CAAC;IAC9I,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,CAAC,WAAW,MAAM,GAAG,CAAC,CAAC,CAAC,0CAA0C,CAAC;AACpF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAyB;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC;IAE7C,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC3D,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3C,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,WAAW,IAAI,QAAQ,EAAE,CAAC,CAAC;IAExD,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACnD,IAAI,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC;IAErD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAC9D,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC7F,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEtF,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC5D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,0BAA0B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE1F,8EAA8E;IAC9E,qEAAqE;IACrE,IAAI,WAAW,IAAI,QAAQ,IAAI,QAAQ,KAAK,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;IAEnG,IAAI,MAAM,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IAElC,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;QACxB,SAAS,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC;QAC7B,QAAQ,EAAE,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAgB,EAChB,OAAqC;IAErC,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE;QACtC,KAAK,MAAM,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,SAAS;gBAAE,SAAS;YACzB,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;YACrC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC9B,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;YACtC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC/D,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,0EAA0E;IAC1E,+EAA+E;IAC/E,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC3C,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QACrD,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QACxD,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,MAAM,cAAc,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzF,OAAO,cAAc,GAAG,aAAa;eAChC,SAAS,GAAG,QAAQ;eACpB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;IACP,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The identifier ledger — provenance for every name that reaches executed SQL.
3
+ *
4
+ * A semantic-layer-mediated agent reached 94.15% on Spider2-snow where frontier
5
+ * models score 17–21% unaided, and the rule doing most of that work is not a
6
+ * better prompt: only identifiers LIFTED FROM A COMPILER OUTPUT may appear in
7
+ * executed SQL. Everything else is a guess wearing a plausible name.
8
+ *
9
+ * The static equivalent already exists — `validateSqlAgainstLocalContext` checks
10
+ * SQL against the context pack's allowed relations. The ledger is its dynamic
11
+ * half: inside a loop the set of PROVEN identifiers grows with every tool
12
+ * observation, so what counts as admissible at step 5 is not what it was at
13
+ * step 1.
14
+ *
15
+ * Division of labour, deliberately: the validator EXTRACTS references from SQL
16
+ * (it owns the dialect handling, CTE scoping, and alias resolution); the ledger
17
+ * only adjudicates where each reference came from. Re-implementing extraction
18
+ * here would fork a parser that has already absorbed a lot of hard-won cases.
19
+ */
20
+ /** Where an identifier was proven. Ordered loosely by strength of evidence. */
21
+ export type IdentifierSource = 'compiler' | 'preview' | 'schema_tool' | 'catalog';
22
+ export interface AdmittedIdentifier {
23
+ identifier: string;
24
+ source: IdentifierSource;
25
+ /** The tool call / receipt that proves it, for the audit trail. */
26
+ receiptId: string;
27
+ }
28
+ export interface LedgerAdjudication {
29
+ ok: boolean;
30
+ /** References that no observation admitted. */
31
+ unadmitted: string[];
32
+ /**
33
+ * Closest admitted identifier per unadmitted reference. The dominant failure
34
+ * mode is a plausible-but-wrong column name, so handing the model the exact
35
+ * correction is worth far more than telling it that something was wrong.
36
+ */
37
+ nearest: Record<string, string>;
38
+ }
39
+ /** Damerau-Levenshtein distance, for "did you mean" corrections. */
40
+ export declare function editDistance(left: string, right: string): number;
41
+ export declare class IdentifierLedger {
42
+ private readonly admitted;
43
+ /**
44
+ * Record identifiers proven by one observation.
45
+ *
46
+ * Admission is append-only and always carries a receipt: an identifier with no
47
+ * traceable source is exactly what this class exists to keep out of SQL.
48
+ */
49
+ admit(source: IdentifierSource, identifiers: readonly string[], receiptId: string): void;
50
+ isAdmitted(identifier: string, options?: {
51
+ requireObserved?: boolean;
52
+ }): boolean;
53
+ size(): number;
54
+ entries(): AdmittedIdentifier[];
55
+ /** The closest admitted identifier, when one is close enough to suggest. */
56
+ nearestAdmitted(identifier: string): string | undefined;
57
+ /**
58
+ * Adjudicate the references a validator extracted from candidate SQL.
59
+ *
60
+ * Returns a report, never a throw: an unadmitted identifier is a correctable
61
+ * observation to feed back into the loop, not a terminal refusal. That
62
+ * distinction is the whole point — the old pipeline turned this into a
63
+ * `grounding_gap` and stopped.
64
+ */
65
+ adjudicate(references: {
66
+ relations?: readonly string[];
67
+ columns?: readonly string[];
68
+ }, options?: {
69
+ requireObserved?: boolean;
70
+ }): LedgerAdjudication;
71
+ /** One line of feedback the model can act on directly. */
72
+ static renderCorrection(adjudication: LedgerAdjudication): string | undefined;
73
+ }
74
+ //# sourceMappingURL=identifier-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifier-ledger.d.ts","sourceRoot":"","sources":["../../src/agentic/identifier-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,SAAS,GACT,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,+CAA+C;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAmBD,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBhE;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAElE;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAaxF,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO;IAMpF,IAAI,IAAI,MAAM;IAId,OAAO,IAAI,kBAAkB,EAAE;IAI/B,4EAA4E;IAC5E,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcvD;;;;;;;OAOG;IACH,UAAU,CACR,UAAU,EAAE;QAAE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAC1E,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,kBAAkB;IAarB,0DAA0D;IAC1D,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS;CAS9E"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The identifier ledger — provenance for every name that reaches executed SQL.
3
+ *
4
+ * A semantic-layer-mediated agent reached 94.15% on Spider2-snow where frontier
5
+ * models score 17–21% unaided, and the rule doing most of that work is not a
6
+ * better prompt: only identifiers LIFTED FROM A COMPILER OUTPUT may appear in
7
+ * executed SQL. Everything else is a guess wearing a plausible name.
8
+ *
9
+ * The static equivalent already exists — `validateSqlAgainstLocalContext` checks
10
+ * SQL against the context pack's allowed relations. The ledger is its dynamic
11
+ * half: inside a loop the set of PROVEN identifiers grows with every tool
12
+ * observation, so what counts as admissible at step 5 is not what it was at
13
+ * step 1.
14
+ *
15
+ * Division of labour, deliberately: the validator EXTRACTS references from SQL
16
+ * (it owns the dialect handling, CTE scoping, and alias resolution); the ledger
17
+ * only adjudicates where each reference came from. Re-implementing extraction
18
+ * here would fork a parser that has already absorbed a lot of hard-won cases.
19
+ */
20
+ /** Case-insensitive qualified identity. Qualifiers remain part of authority. */
21
+ function normalizeIdentifier(value) {
22
+ return value
23
+ .trim()
24
+ .split('.')
25
+ .map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
26
+ .filter(Boolean)
27
+ .join('.');
28
+ }
29
+ /** Last segment of a dotted identifier (`db.schema.orders` → `orders`). */
30
+ function leafOf(value) {
31
+ const normalized = normalizeIdentifier(value);
32
+ const parts = normalized.split('.').filter(Boolean);
33
+ return parts[parts.length - 1] ?? normalized;
34
+ }
35
+ /** Damerau-Levenshtein distance, for "did you mean" corrections. */
36
+ export function editDistance(left, right) {
37
+ if (left === right)
38
+ return 0;
39
+ const rows = left.length + 1;
40
+ const cols = right.length + 1;
41
+ const grid = Array.from({ length: rows }, () => new Array(cols).fill(0));
42
+ for (let i = 0; i < rows; i += 1)
43
+ grid[i][0] = i;
44
+ for (let j = 0; j < cols; j += 1)
45
+ grid[0][j] = j;
46
+ for (let i = 1; i < rows; i += 1) {
47
+ for (let j = 1; j < cols; j += 1) {
48
+ const cost = left[i - 1] === right[j - 1] ? 0 : 1;
49
+ let best = Math.min(grid[i - 1][j] + 1, grid[i][j - 1] + 1, grid[i - 1][j - 1] + cost);
50
+ if (i > 1 && j > 1 && left[i - 1] === right[j - 2] && left[i - 2] === right[j - 1]) {
51
+ best = Math.min(best, grid[i - 2][j - 2] + 1);
52
+ }
53
+ grid[i][j] = best;
54
+ }
55
+ }
56
+ return grid[rows - 1][cols - 1];
57
+ }
58
+ export class IdentifierLedger {
59
+ admitted = new Map();
60
+ /**
61
+ * Record identifiers proven by one observation.
62
+ *
63
+ * Admission is append-only and always carries a receipt: an identifier with no
64
+ * traceable source is exactly what this class exists to keep out of SQL.
65
+ */
66
+ admit(source, identifiers, receiptId) {
67
+ for (const raw of identifiers) {
68
+ if (!raw || typeof raw !== 'string')
69
+ continue;
70
+ const key = normalizeIdentifier(raw);
71
+ if (!key)
72
+ continue;
73
+ const existing = this.admitted.get(key);
74
+ // Keep the strongest provenance rather than the most recent one.
75
+ if (!existing || sourceRank(source) > sourceRank(existing.source)) {
76
+ this.admitted.set(key, { identifier: raw, source, receiptId });
77
+ }
78
+ }
79
+ }
80
+ isAdmitted(identifier, options = {}) {
81
+ const key = normalizeIdentifier(identifier);
82
+ const entry = this.admitted.get(key);
83
+ return Boolean(entry && (!options.requireObserved || entry.source !== 'catalog'));
84
+ }
85
+ size() {
86
+ return this.admitted.size;
87
+ }
88
+ entries() {
89
+ return [...this.admitted.values()];
90
+ }
91
+ /** The closest admitted identifier, when one is close enough to suggest. */
92
+ nearestAdmitted(identifier) {
93
+ const needle = leafOf(identifier);
94
+ let best;
95
+ for (const entry of this.admitted.values()) {
96
+ const distance = editDistance(needle, leafOf(entry.identifier));
97
+ if (!best || distance < best.distance)
98
+ best = { identifier: entry.identifier, distance };
99
+ }
100
+ if (!best)
101
+ return undefined;
102
+ // Only suggest a genuine near-miss. A distant "correction" reads as a
103
+ // confident wrong answer and sends the model further off course.
104
+ const tolerance = Math.max(2, Math.floor(needle.length / 3));
105
+ return best.distance <= tolerance ? best.identifier : undefined;
106
+ }
107
+ /**
108
+ * Adjudicate the references a validator extracted from candidate SQL.
109
+ *
110
+ * Returns a report, never a throw: an unadmitted identifier is a correctable
111
+ * observation to feed back into the loop, not a terminal refusal. That
112
+ * distinction is the whole point — the old pipeline turned this into a
113
+ * `grounding_gap` and stopped.
114
+ */
115
+ adjudicate(references, options = {}) {
116
+ const unadmitted = [];
117
+ const nearest = {};
118
+ for (const reference of [...(references.relations ?? []), ...(references.columns ?? [])]) {
119
+ if (!reference || this.isAdmitted(reference, options))
120
+ continue;
121
+ if (unadmitted.includes(reference))
122
+ continue;
123
+ unadmitted.push(reference);
124
+ const suggestion = this.nearestAdmitted(reference);
125
+ if (suggestion)
126
+ nearest[reference] = suggestion;
127
+ }
128
+ return { ok: unadmitted.length === 0, unadmitted, nearest };
129
+ }
130
+ /** One line of feedback the model can act on directly. */
131
+ static renderCorrection(adjudication) {
132
+ if (adjudication.ok)
133
+ return undefined;
134
+ const parts = adjudication.unadmitted.map((reference) => {
135
+ const suggestion = adjudication.nearest[reference];
136
+ return suggestion ? `${reference} (did you mean ${suggestion}?)` : reference;
137
+ });
138
+ return `These identifiers were never returned by a tool, so they cannot be executed: ${parts.join(', ')}. `
139
+ + 'Use only names that appeared in a compile, preview, or schema result.';
140
+ }
141
+ }
142
+ function sourceRank(source) {
143
+ switch (source) {
144
+ case 'compiler': return 4;
145
+ case 'preview': return 3;
146
+ case 'schema_tool': return 2;
147
+ case 'catalog': return 1;
148
+ default: return 0;
149
+ }
150
+ }
151
+ //# sourceMappingURL=identifier-ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifier-ledger.js","sourceRoot":"","sources":["../../src/agentic/identifier-ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Ledger enforcement over a tool loop.
3
+ *
4
+ * The rule that carries most of the 94.15%-on-Spider2 result is not a better
5
+ * prompt: only identifiers LIFTED FROM A TOOL OUTPUT may appear in executed SQL.
6
+ * Prompting for it does not work — a model asked to "only use real columns" will
7
+ * still invent `customer_tier` when the schema says `service_tier`, because the
8
+ * invented name is more plausible than the real one.
9
+ *
10
+ * So it is enforced structurally. Every tool result is harvested into an
11
+ * {@link IdentifierLedger} as the loop runs, and the SQL the model finally
12
+ * proposes is adjudicated against what was actually observed. A violation comes
13
+ * back as a correction naming the nearest observed identifier — not as a
14
+ * refusal, which is what the old pipeline turned it into.
15
+ */
16
+ import type { AgentToolDefinition } from '../providers/types.js';
17
+ import { IdentifierLedger, type IdentifierSource } from './identifier-ledger.js';
18
+ export declare function evidenceSourceForTool(toolName: string): IdentifierSource;
19
+ /**
20
+ * Pull identifiers out of an arbitrary tool result.
21
+ *
22
+ * Deliberately structural rather than a schema per tool: the tool set grows, and
23
+ * a harvester that silently misses a new tool's output would quietly re-open the
24
+ * hole this class exists to close — every unharvested name becomes an
25
+ * "unadmitted" false alarm on correct SQL.
26
+ *
27
+ * Bounded in depth and count so a large preview cannot turn one observation into
28
+ * a pathological walk.
29
+ */
30
+ export declare function harvestIdentifiers(output: unknown, limit?: number): string[];
31
+ /**
32
+ * Wrap tools so every observation feeds the ledger.
33
+ *
34
+ * The wrapper is transparent: it returns the tool's own output untouched and
35
+ * never fails the call. A harvesting bug must not break a working tool — the
36
+ * worst case is a name that goes unadmitted and surfaces as a correction, which
37
+ * the loop can recover from.
38
+ */
39
+ export declare function withLedgerHarvest(tools: readonly AgentToolDefinition[], ledger: IdentifierLedger, onObservation?: (event: {
40
+ tool: string;
41
+ admitted: number;
42
+ }) => void): AgentToolDefinition[];
43
+ /**
44
+ * The explore-then-compose discipline, as a system message.
45
+ *
46
+ * Mirrors the two phases that produced the 94% result: establish real names
47
+ * first, then assemble over only those names.
48
+ */
49
+ export declare const ANALYST_TOOL_POLICY: string;
50
+ /**
51
+ * Adjudicate proposed SQL against what the loop actually observed.
52
+ *
53
+ * Returns a correction to feed back, or `undefined` when everything checks out.
54
+ * Never throws and never refuses: an unadmitted identifier is a fixable mistake,
55
+ * and the old pipeline turning it into a terminal `grounding_gap` is precisely
56
+ * the dead end being removed.
57
+ */
58
+ export declare function adjudicateProposedSql(ledger: IdentifierLedger, references: {
59
+ relations?: readonly string[];
60
+ columns?: readonly string[];
61
+ }): {
62
+ ok: true;
63
+ } | {
64
+ ok: false;
65
+ correction: string;
66
+ unadmitted: string[];
67
+ };
68
+ //# sourceMappingURL=ledger-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-tools.d.ts","sourceRoot":"","sources":["../../src/agentic/ledger-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAyBjF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAExE;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,SAAM,GAAG,MAAM,EAAE,CAsEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,MAAM,EAAE,gBAAgB,EACxB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,GAClE,mBAAmB,EAAE,CAevB;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAgBpB,CAAC;AAEb;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE;IAAE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACzE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAWxE"}
@@ -0,0 +1,198 @@
1
+ import { IdentifierLedger } from './identifier-ledger.js';
2
+ /**
3
+ * Where each tool's output sits on the evidence scale.
4
+ *
5
+ * A compiler emitting SQL is the strongest claim that a name exists; a catalog
6
+ * search only says something was INDEXED under that name, which is a weaker
7
+ * thing entirely — a stale index still returns rows.
8
+ */
9
+ const TOOL_EVIDENCE = {
10
+ compile_semantic_query: 'compiler',
11
+ compile_governed_query: 'compiler',
12
+ preview_query: 'preview',
13
+ sample_notebook_dataset: 'preview',
14
+ execute_local_analysis: 'preview',
15
+ get_table_schema: 'schema_tool',
16
+ describe_notebook_dataset: 'schema_tool',
17
+ inspect_metadata_context: 'schema_tool',
18
+ list_notebook_datasets: 'schema_tool',
19
+ scan_manifest: 'catalog',
20
+ search_semantic_layer: 'catalog',
21
+ search_metadata: 'catalog',
22
+ check_compatibility: 'catalog',
23
+ };
24
+ export function evidenceSourceForTool(toolName) {
25
+ return TOOL_EVIDENCE[toolName] ?? 'catalog';
26
+ }
27
+ /** Keys whose STRING values name a relation or column. */
28
+ const IDENTIFIER_KEYS = new Set([
29
+ 'relation', 'table', 'tableName', 'fullName', 'qualifiedName', 'name',
30
+ 'column', 'columnName', 'dimension', 'metric', 'measure', 'resolvedName',
31
+ ]);
32
+ /** Keys whose ARRAY values are lists of identifiers or column records. */
33
+ const IDENTIFIER_LIST_KEYS = new Set([
34
+ 'columns', 'relations', 'tables', 'dimensions', 'metrics', 'measures',
35
+ 'compatibleDimensions', 'outputs', 'groupBy', 'selectedColumns',
36
+ ]);
37
+ /**
38
+ * Pull identifiers out of an arbitrary tool result.
39
+ *
40
+ * Deliberately structural rather than a schema per tool: the tool set grows, and
41
+ * a harvester that silently misses a new tool's output would quietly re-open the
42
+ * hole this class exists to close — every unharvested name becomes an
43
+ * "unadmitted" false alarm on correct SQL.
44
+ *
45
+ * Bounded in depth and count so a large preview cannot turn one observation into
46
+ * a pathological walk.
47
+ */
48
+ export function harvestIdentifiers(output, limit = 500) {
49
+ const found = [];
50
+ const seen = new Set();
51
+ const push = (value) => {
52
+ if (typeof value !== 'string')
53
+ return;
54
+ const trimmed = value.trim();
55
+ // A SQL string or a sentence is not an identifier. Bare-ish names only.
56
+ if (!trimmed || trimmed.length > 128 || /\s/.test(trimmed))
57
+ return;
58
+ if (seen.has(trimmed) || found.length >= limit)
59
+ return;
60
+ seen.add(trimmed);
61
+ found.push(trimmed);
62
+ };
63
+ const walk = (node, depth) => {
64
+ if (found.length >= limit || depth > 6 || node === null || node === undefined)
65
+ return;
66
+ if (Array.isArray(node)) {
67
+ for (const entry of node) {
68
+ // A bare string inside an identifier list is itself an identifier.
69
+ if (typeof entry === 'string')
70
+ push(entry);
71
+ else
72
+ walk(entry, depth + 1);
73
+ }
74
+ return;
75
+ }
76
+ if (typeof node !== 'object')
77
+ return;
78
+ const record = node;
79
+ // Schema/preview tools commonly return `{ relation, columns: [{ name }] }`.
80
+ // Preserve that ownership in the ledger. A bare `id` is not an execution
81
+ // identity: it could belong to any relation in the same result.
82
+ const relation = [record.relation, record.table, record.fullName, record.qualifiedName]
83
+ .find((value) => typeof value === 'string' && value.trim().length > 0)
84
+ ?.trim();
85
+ const ownedColumnKeys = new Set(['columns', 'selectedColumns', 'outputs']);
86
+ if (relation) {
87
+ for (const key of ['columns', 'selectedColumns', 'outputs']) {
88
+ const values = record[key];
89
+ if (!Array.isArray(values))
90
+ continue;
91
+ for (const entry of values) {
92
+ const column = typeof entry === 'string'
93
+ ? entry
94
+ : entry && typeof entry === 'object'
95
+ ? [
96
+ entry.column,
97
+ entry.name,
98
+ entry.columnName,
99
+ ].find((value) => typeof value === 'string')
100
+ : undefined;
101
+ if (column && !/\s/.test(column))
102
+ push(`${relation}.${column}`);
103
+ }
104
+ }
105
+ }
106
+ for (const [key, value] of Object.entries(record)) {
107
+ if (IDENTIFIER_KEYS.has(key))
108
+ push(value);
109
+ if (IDENTIFIER_LIST_KEYS.has(key) && Array.isArray(value)) {
110
+ // Relation-shaped schema and preview payloads were already harvested
111
+ // above as `relation.column`. Re-admitting their bare column leaves
112
+ // would let one table authorize the same leaf on another table.
113
+ if (relation && ownedColumnKeys.has(key))
114
+ continue;
115
+ for (const entry of value) {
116
+ if (typeof entry === 'string')
117
+ push(entry);
118
+ else
119
+ walk(entry, depth + 1);
120
+ }
121
+ continue;
122
+ }
123
+ walk(value, depth + 1);
124
+ }
125
+ };
126
+ walk(output, 0);
127
+ return found;
128
+ }
129
+ /**
130
+ * Wrap tools so every observation feeds the ledger.
131
+ *
132
+ * The wrapper is transparent: it returns the tool's own output untouched and
133
+ * never fails the call. A harvesting bug must not break a working tool — the
134
+ * worst case is a name that goes unadmitted and surfaces as a correction, which
135
+ * the loop can recover from.
136
+ */
137
+ export function withLedgerHarvest(tools, ledger, onObservation) {
138
+ return tools.map((tool, index) => ({
139
+ ...tool,
140
+ run: async (args) => {
141
+ const output = await tool.run(args);
142
+ try {
143
+ const identifiers = harvestIdentifiers(output);
144
+ ledger.admit(evidenceSourceForTool(tool.name), identifiers, `${tool.name}#${index}`);
145
+ onObservation?.({ tool: tool.name, admitted: identifiers.length });
146
+ }
147
+ catch {
148
+ // Never let harvesting break a tool that worked.
149
+ }
150
+ return output;
151
+ },
152
+ }));
153
+ }
154
+ /**
155
+ * The explore-then-compose discipline, as a system message.
156
+ *
157
+ * Mirrors the two phases that produced the 94% result: establish real names
158
+ * first, then assemble over only those names.
159
+ */
160
+ export const ANALYST_TOOL_POLICY = [
161
+ 'Work in two phases.',
162
+ '',
163
+ 'EXPLORE — establish what actually exists before writing any SQL:',
164
+ ' · search for the governed metrics and dimensions the question needs',
165
+ ' · check compatibility before assuming a breakdown is reachable; when it is not,',
166
+ ' the tool tells you which dimensions ARE reachable — use one of those rather',
167
+ ' than stopping',
168
+ ' · compile a governed query and read back the identifiers it returns',
169
+ '',
170
+ 'COMPOSE — assemble the final query using ONLY names a tool returned to you.',
171
+ 'Every table and column in executed SQL must have appeared in a compile, preview,',
172
+ 'or schema result. Preserve the qualified relation.column identity returned by the tool;',
173
+ 'a bare leaf is not enough when multiple relations can contain the same name. A name that merely looks plausible is the single most common',
174
+ 'way these queries go wrong: if you need a column you have not seen, inspect it',
175
+ 'first rather than guessing its spelling.',
176
+ ].join('\n');
177
+ /**
178
+ * Adjudicate proposed SQL against what the loop actually observed.
179
+ *
180
+ * Returns a correction to feed back, or `undefined` when everything checks out.
181
+ * Never throws and never refuses: an unadmitted identifier is a fixable mistake,
182
+ * and the old pipeline turning it into a terminal `grounding_gap` is precisely
183
+ * the dead end being removed.
184
+ */
185
+ export function adjudicateProposedSql(ledger, references) {
186
+ // Catalog retrieval narrows tool search but is never execution evidence. A
187
+ // generated statement must name identifiers a compiler, preview, or schema
188
+ // observation actually returned during this invocation.
189
+ const verdict = ledger.adjudicate(references, { requireObserved: true });
190
+ if (verdict.ok)
191
+ return { ok: true };
192
+ return {
193
+ ok: false,
194
+ unadmitted: verdict.unadmitted,
195
+ correction: IdentifierLedger.renderCorrection(verdict) ?? 'Some identifiers were never observed.',
196
+ };
197
+ }
198
+ //# sourceMappingURL=ledger-tools.js.map