@wrenai/genbi 0.0.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.
- package/LICENSE +201 -0
- package/README.md +141 -0
- package/bin/genbi.mjs +31 -0
- package/dist/assets/echarts-LTFC-ysd.js +40 -0
- package/dist/assets/index-Bo--QMUN.js +441 -0
- package/dist/assets/index-Xs4q7pNo.css +1 -0
- package/dist/index.html +14 -0
- package/dist/wrenai_logo.png +0 -0
- package/dist/wrenai_logo_white.png +0 -0
- package/dist-server/harness/auth/detect.d.ts +18 -0
- package/dist-server/harness/auth/detect.js +37 -0
- package/dist-server/harness/auth/index.d.ts +4 -0
- package/dist-server/harness/auth/index.js +2 -0
- package/dist-server/harness/auth/probe.d.ts +18 -0
- package/dist-server/harness/auth/probe.js +52 -0
- package/dist-server/harness/auth/types.d.ts +42 -0
- package/dist-server/harness/auth/types.js +9 -0
- package/dist-server/harness/bundle/loader.d.ts +32 -0
- package/dist-server/harness/bundle/loader.js +43 -0
- package/dist-server/harness/bundle/schema.d.ts +199 -0
- package/dist-server/harness/bundle/schema.js +127 -0
- package/dist-server/harness/bundle/version.d.ts +10 -0
- package/dist-server/harness/bundle/version.js +43 -0
- package/dist-server/harness/capability/gate.d.ts +6 -0
- package/dist-server/harness/capability/gate.js +29 -0
- package/dist-server/harness/capability/registry.d.ts +6 -0
- package/dist-server/harness/capability/registry.js +17 -0
- package/dist-server/harness/cli-args.d.ts +123 -0
- package/dist-server/harness/cli-args.js +247 -0
- package/dist-server/harness/cli.d.ts +2 -0
- package/dist-server/harness/cli.js +151 -0
- package/dist-server/harness/compile/cache.d.ts +23 -0
- package/dist-server/harness/compile/cache.js +109 -0
- package/dist-server/harness/compile/compose-profile.d.ts +34 -0
- package/dist-server/harness/compile/compose-profile.js +95 -0
- package/dist-server/harness/compile/errors.d.ts +22 -0
- package/dist-server/harness/compile/errors.js +41 -0
- package/dist-server/harness/compile/fingerprint.d.ts +22 -0
- package/dist-server/harness/compile/fingerprint.js +84 -0
- package/dist-server/harness/compile/index.d.ts +8 -0
- package/dist-server/harness/compile/index.js +7 -0
- package/dist-server/harness/compile/pipeline.d.ts +66 -0
- package/dist-server/harness/compile/pipeline.js +176 -0
- package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
- package/dist-server/harness/compile/resolve-binary.js +133 -0
- package/dist-server/harness/compile/types.d.ts +107 -0
- package/dist-server/harness/compile/types.js +1 -0
- package/dist-server/harness/compile/warble-identity.d.ts +12 -0
- package/dist-server/harness/compile/warble-identity.js +33 -0
- package/dist-server/harness/compliance/gate.d.ts +35 -0
- package/dist-server/harness/compliance/gate.js +48 -0
- package/dist-server/harness/compliance/index.d.ts +2 -0
- package/dist-server/harness/compliance/index.js +1 -0
- package/dist-server/harness/compliance/types.d.ts +16 -0
- package/dist-server/harness/compliance/types.js +1 -0
- package/dist-server/harness/events/emitter.d.ts +34 -0
- package/dist-server/harness/events/emitter.js +25 -0
- package/dist-server/harness/events/index.d.ts +4 -0
- package/dist-server/harness/events/index.js +2 -0
- package/dist-server/harness/events/sse.d.ts +20 -0
- package/dist-server/harness/events/sse.js +48 -0
- package/dist-server/harness/events/types.d.ts +131 -0
- package/dist-server/harness/events/types.js +1 -0
- package/dist-server/harness/exec/errors.d.ts +16 -0
- package/dist-server/harness/exec/errors.js +29 -0
- package/dist-server/harness/exec/index.d.ts +5 -0
- package/dist-server/harness/exec/index.js +2 -0
- package/dist-server/harness/exec/local.d.ts +32 -0
- package/dist-server/harness/exec/local.js +134 -0
- package/dist-server/harness/exec/policy.d.ts +12 -0
- package/dist-server/harness/exec/policy.js +1 -0
- package/dist-server/harness/exec/types.d.ts +63 -0
- package/dist-server/harness/exec/types.js +1 -0
- package/dist-server/harness/guardrails/index.d.ts +2 -0
- package/dist-server/harness/guardrails/index.js +1 -0
- package/dist-server/harness/guardrails/policy.d.ts +45 -0
- package/dist-server/harness/guardrails/policy.js +57 -0
- package/dist-server/harness/index.d.ts +37 -0
- package/dist-server/harness/index.js +19 -0
- package/dist-server/harness/loop/errors.d.ts +53 -0
- package/dist-server/harness/loop/errors.js +72 -0
- package/dist-server/harness/loop/executor.d.ts +123 -0
- package/dist-server/harness/loop/executor.js +380 -0
- package/dist-server/harness/loop/guard.d.ts +16 -0
- package/dist-server/harness/loop/guard.js +1 -0
- package/dist-server/harness/loop/index.d.ts +5 -0
- package/dist-server/harness/loop/index.js +3 -0
- package/dist-server/harness/native-producer/index.d.ts +109 -0
- package/dist-server/harness/native-producer/index.js +302 -0
- package/dist-server/harness/npm-package-resolve.d.ts +28 -0
- package/dist-server/harness/npm-package-resolve.js +47 -0
- package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
- package/dist-server/harness/providers/adapters/anthropic.js +6 -0
- package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
- package/dist-server/harness/providers/adapters/mock.js +5 -0
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
- package/dist-server/harness/providers/binding.d.ts +23 -0
- package/dist-server/harness/providers/binding.js +13 -0
- package/dist-server/harness/providers/errors.d.ts +6 -0
- package/dist-server/harness/providers/errors.js +12 -0
- package/dist-server/harness/providers/index.d.ts +18 -0
- package/dist-server/harness/providers/index.js +22 -0
- package/dist-server/harness/providers/registry.d.ts +16 -0
- package/dist-server/harness/providers/registry.js +24 -0
- package/dist-server/harness/render/envelope.d.ts +145 -0
- package/dist-server/harness/render/envelope.js +428 -0
- package/dist-server/harness/render/errors.d.ts +33 -0
- package/dist-server/harness/render/errors.js +46 -0
- package/dist-server/harness/render/index.d.ts +5 -0
- package/dist-server/harness/render/index.js +3 -0
- package/dist-server/harness/render/validate.d.ts +13 -0
- package/dist-server/harness/render/validate.js +88 -0
- package/dist-server/harness/route/adapter-spec.d.ts +40 -0
- package/dist-server/harness/route/adapter-spec.js +61 -0
- package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
- package/dist-server/harness/route/agent-sdk-cli.js +86 -0
- package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
- package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
- package/dist-server/harness/route/artifact-content.d.ts +53 -0
- package/dist-server/harness/route/artifact-content.js +83 -0
- package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
- package/dist-server/harness/route/chat-event-mapper.js +164 -0
- package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
- package/dist-server/harness/route/codex-ask-events.js +395 -0
- package/dist-server/harness/route/codex-ask.d.ts +15 -0
- package/dist-server/harness/route/codex-ask.js +242 -0
- package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
- package/dist-server/harness/route/codex-local-cli.js +78 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
- package/dist-server/harness/route/codex-local-manifest.js +180 -0
- package/dist-server/harness/route/describe.d.ts +54 -0
- package/dist-server/harness/route/describe.js +84 -0
- package/dist-server/harness/route/dispatched.d.ts +138 -0
- package/dist-server/harness/route/dispatched.js +339 -0
- package/dist-server/harness/route/in-process.d.ts +43 -0
- package/dist-server/harness/route/in-process.js +106 -0
- package/dist-server/harness/route/index.d.ts +19 -0
- package/dist-server/harness/route/index.js +12 -0
- package/dist-server/harness/route/profile-source.d.ts +37 -0
- package/dist-server/harness/route/profile-source.js +76 -0
- package/dist-server/harness/route/route.d.ts +33 -0
- package/dist-server/harness/route/route.js +114 -0
- package/dist-server/harness/route/tier-binding.d.ts +36 -0
- package/dist-server/harness/route/tier-binding.js +63 -0
- package/dist-server/harness/route/types.d.ts +286 -0
- package/dist-server/harness/route/types.js +1 -0
- package/dist-server/harness/session/errors.d.ts +4 -0
- package/dist-server/harness/session/errors.js +7 -0
- package/dist-server/harness/session/gate.d.ts +11 -0
- package/dist-server/harness/session/gate.js +13 -0
- package/dist-server/harness/session/index.d.ts +5 -0
- package/dist-server/harness/session/index.js +3 -0
- package/dist-server/harness/session/run.d.ts +60 -0
- package/dist-server/harness/session/run.js +333 -0
- package/dist-server/harness/session/types.d.ts +29 -0
- package/dist-server/harness/session/types.js +1 -0
- package/dist-server/harness/setup/codex-events.d.ts +12 -0
- package/dist-server/harness/setup/codex-events.js +108 -0
- package/dist-server/harness/setup/runner.d.ts +497 -0
- package/dist-server/harness/setup/runner.js +1314 -0
- package/dist-server/harness/tools/errors.d.ts +107 -0
- package/dist-server/harness/tools/errors.js +141 -0
- package/dist-server/harness/tools/index.d.ts +13 -0
- package/dist-server/harness/tools/index.js +8 -0
- package/dist-server/harness/tools/mcp.d.ts +26 -0
- package/dist-server/harness/tools/mcp.js +15 -0
- package/dist-server/harness/tools/native.d.ts +179 -0
- package/dist-server/harness/tools/native.js +322 -0
- package/dist-server/harness/tools/parse.d.ts +14 -0
- package/dist-server/harness/tools/parse.js +18 -0
- package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
- package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
- package/dist-server/harness/tools/resolve.d.ts +51 -0
- package/dist-server/harness/tools/resolve.js +79 -0
- package/dist-server/harness/tools/setup-native.d.ts +84 -0
- package/dist-server/harness/tools/setup-native.js +353 -0
- package/dist-server/harness/tools/wiring.d.ts +10 -0
- package/dist-server/harness/tools/wiring.js +16 -0
- package/dist-server/providers/setup.provider.yaml +31 -0
- package/dist-server/providers/wren.provider.yaml +33 -0
- package/dist-server/server/adopt.d.ts +87 -0
- package/dist-server/server/adopt.js +263 -0
- package/dist-server/server/app.d.ts +6 -0
- package/dist-server/server/app.js +2521 -0
- package/dist-server/server/auth-choice.d.ts +16 -0
- package/dist-server/server/auth-choice.js +36 -0
- package/dist-server/server/bin.d.ts +2 -0
- package/dist-server/server/bin.js +601 -0
- package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
- package/dist-server/server/bootstrap-project-binding.js +71 -0
- package/dist-server/server/clarify.d.ts +10 -0
- package/dist-server/server/clarify.js +17 -0
- package/dist-server/server/codex-setup-mcp.d.ts +2 -0
- package/dist-server/server/codex-setup-mcp.js +147 -0
- package/dist-server/server/compose.d.ts +89 -0
- package/dist-server/server/compose.js +271 -0
- package/dist-server/server/conn-config.d.ts +30 -0
- package/dist-server/server/conn-config.js +253 -0
- package/dist-server/server/context-files.d.ts +12 -0
- package/dist-server/server/context-files.js +117 -0
- package/dist-server/server/context-map.d.ts +30 -0
- package/dist-server/server/context-map.js +95 -0
- package/dist-server/server/context-source.d.ts +68 -0
- package/dist-server/server/context-source.js +85 -0
- package/dist-server/server/db.d.ts +862 -0
- package/dist-server/server/db.js +1839 -0
- package/dist-server/server/enrichment-runner.d.ts +184 -0
- package/dist-server/server/enrichment-runner.js +213 -0
- package/dist-server/server/enrichment-verify.d.ts +73 -0
- package/dist-server/server/enrichment-verify.js +290 -0
- package/dist-server/server/enrichment.d.ts +232 -0
- package/dist-server/server/enrichment.js +199 -0
- package/dist-server/server/env-detect.d.ts +8 -0
- package/dist-server/server/env-detect.js +17 -0
- package/dist-server/server/env-field-hints.d.ts +46 -0
- package/dist-server/server/env-field-hints.js +48 -0
- package/dist-server/server/fold.d.ts +133 -0
- package/dist-server/server/fold.js +407 -0
- package/dist-server/server/harness-profile-sources.d.ts +3 -0
- package/dist-server/server/harness-profile-sources.js +24 -0
- package/dist-server/server/harness.d.ts +19 -0
- package/dist-server/server/harness.js +439 -0
- package/dist-server/server/impact.d.ts +18 -0
- package/dist-server/server/impact.js +85 -0
- package/dist-server/server/interactive-terminal.d.ts +175 -0
- package/dist-server/server/interactive-terminal.js +336 -0
- package/dist-server/server/native-artifacts.d.ts +1004 -0
- package/dist-server/server/native-artifacts.js +564 -0
- package/dist-server/server/native-dispatch-registry.d.ts +42 -0
- package/dist-server/server/native-dispatch-registry.js +45 -0
- package/dist-server/server/native-producer-fence.d.ts +14 -0
- package/dist-server/server/native-producer-fence.js +48 -0
- package/dist-server/server/native-session-resume.d.ts +6 -0
- package/dist-server/server/native-session-resume.js +103 -0
- package/dist-server/server/native-session-workspace.d.ts +40 -0
- package/dist-server/server/native-session-workspace.js +175 -0
- package/dist-server/server/native-sessions.d.ts +374 -0
- package/dist-server/server/native-sessions.js +1517 -0
- package/dist-server/server/native-wren-runtime.d.ts +20 -0
- package/dist-server/server/native-wren-runtime.js +259 -0
- package/dist-server/server/node-pty-host.d.ts +31 -0
- package/dist-server/server/node-pty-host.js +100 -0
- package/dist-server/server/route-intent.d.ts +40 -0
- package/dist-server/server/route-intent.js +23 -0
- package/dist-server/server/runtime-binding.d.ts +52 -0
- package/dist-server/server/runtime-binding.js +258 -0
- package/dist-server/server/source-catalog.d.ts +84 -0
- package/dist-server/server/source-catalog.js +221 -0
- package/dist-server/server/source-driver.d.ts +35 -0
- package/dist-server/server/source-driver.js +124 -0
- package/dist-server/server/spa.d.ts +2 -0
- package/dist-server/server/spa.js +51 -0
- package/dist-server/server/subscription-model-catalog.d.ts +27 -0
- package/dist-server/server/subscription-model-catalog.js +203 -0
- package/dist-server/server/turn.d.ts +249 -0
- package/dist-server/server/turn.js +1074 -0
- package/dist-server/server/wire-types.d.ts +799 -0
- package/dist-server/server/wire-types.js +1 -0
- package/dist-server/server/wren-home.d.ts +19 -0
- package/dist-server/server/wren-home.js +33 -0
- package/dist-server/server/wren-profiles.d.ts +43 -0
- package/dist-server/server/wren-profiles.js +123 -0
- package/package.json +93 -0
- package/profiles/genbi-default/context/binding.yml +9 -0
- package/profiles/genbi-default/ir.golden.json +506 -0
- package/profiles/genbi-default/profile.yml +16 -0
- package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
- package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
- package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
- package/profiles/genbi-enrich-context/context/binding.yml +4 -0
- package/profiles/genbi-enrich-context/ir.golden.json +302 -0
- package/profiles/genbi-enrich-context/profile.yml +15 -0
- package/profiles/genbi-monitor/context/binding.yml +7 -0
- package/profiles/genbi-monitor/ir.golden.json +188 -0
- package/profiles/genbi-monitor/profile.yml +27 -0
- package/profiles/genbi-setup/components/build_context/component.yml +44 -0
- package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
- package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
- package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
- package/profiles/genbi-setup/context/binding.yml +8 -0
- package/profiles/genbi-setup/ir.golden.json +140 -0
- package/profiles/genbi-setup/profile.yml +18 -0
- package/profiles/genbi-setup/raw/schema.json +5 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refutes an enrichment proposal before a human is asked to approve it.
|
|
3
|
+
*
|
|
4
|
+
* Everything capable of refuting a proposal — `wren context validate`, `wren
|
|
5
|
+
* context build`, planning its references through the MDL — used to run inside
|
|
6
|
+
* apply, downstream of the approval. So the person was asked to approve
|
|
7
|
+
* something no machine had checked, and the machine checked it afterwards.
|
|
8
|
+
* That ordering does not scale: enrichment output is routinely larger than
|
|
9
|
+
* anyone will read, so approval degrades into a rubber stamp — which is worse
|
|
10
|
+
* than no gate, because it looks like review happened.
|
|
11
|
+
*
|
|
12
|
+
* The checks here need no correct answer to compare against. The engine is the
|
|
13
|
+
* ground truth: a measure over a column that does not exist cannot be planned,
|
|
14
|
+
* whatever anyone believes about it.
|
|
15
|
+
*
|
|
16
|
+
* MECHANISM. A proposal is not on disk, and must not be written into the bound
|
|
17
|
+
* project to find out whether it is sound. The host copies the project to a
|
|
18
|
+
* temporary directory, writes the proposal there, runs the ladder, and discards
|
|
19
|
+
* the copy. The bound project is never touched, so there is nothing to roll
|
|
20
|
+
* back — not even when a check throws.
|
|
21
|
+
*
|
|
22
|
+
* The shadow is host-owned. Giving the agent write access to a project copy is
|
|
23
|
+
* giving it write access that can leak to the real one.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE HOST RUNS THESE ITSELF. The agent holds read-only execution and can
|
|
26
|
+
* run the same commands; it should, because doing so reduces the rounds needed
|
|
27
|
+
* here. But its report is not evidence. "The model says validate passed" is the
|
|
28
|
+
* same failure as the model-authored hashes a live draft once produced, which
|
|
29
|
+
* is why proposal hashing is host-owned too.
|
|
30
|
+
*/
|
|
31
|
+
import { execFile } from "node:child_process";
|
|
32
|
+
import { cp, lstat, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
33
|
+
import { tmpdir } from "node:os";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
export const VERIFICATION_LADDER = ["sink", "grammar", "validate", "build", "dry_plan", "dry_run"];
|
|
36
|
+
/**
|
|
37
|
+
* The Wren keys each change kind's draft must be written in. The first live
|
|
38
|
+
* draft to reach this stage used `model`/`columns`/`metrics`/`relationships` —
|
|
39
|
+
* plausible-looking, and not Wren's grammar at all.
|
|
40
|
+
*/
|
|
41
|
+
const REQUIRED_KEYS = {
|
|
42
|
+
new_cube: ["base_object"],
|
|
43
|
+
new_view: ["statement"],
|
|
44
|
+
};
|
|
45
|
+
/** Keys that indicate a different product's grammar rather than Wren's. */
|
|
46
|
+
const FOREIGN_KEYS = {
|
|
47
|
+
new_cube: ["columns", "metrics", "model"],
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The first path component of `sink` that is a symlink, if any. Checked inside
|
|
51
|
+
* the shadow, where `cp` has already reproduced the bound project's own links.
|
|
52
|
+
*/
|
|
53
|
+
async function symlinkComponent(root, sink) {
|
|
54
|
+
const parts = sink.split("/").filter((part) => part.length > 0);
|
|
55
|
+
let current = root;
|
|
56
|
+
for (const part of parts) {
|
|
57
|
+
current = path.join(current, part);
|
|
58
|
+
try {
|
|
59
|
+
if ((await lstat(current)).isSymbolicLink())
|
|
60
|
+
return path.relative(root, current);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined; // does not exist yet — nothing to follow
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Every call below runs with `cwd` set to the shadow copy, but `wren` does
|
|
70
|
+
* not resolve its project from cwd alone: `WREN_PROJECT_HOME`, when set,
|
|
71
|
+
* wins over cwd-based discovery (see `discover_project_path()` in
|
|
72
|
+
* `core/wren/src/wren/context.py`), and an explicit `--mdl`/`--path` would
|
|
73
|
+
* win over both. If this process's own ambient environment ever carries
|
|
74
|
+
* `WREN_PROJECT_HOME` — as GenBI itself sets it for native session children
|
|
75
|
+
* (`server/native-sessions.ts`, `server/bin.ts`) — every step below would
|
|
76
|
+
* silently validate/build/query the BOUND project instead of the shadow,
|
|
77
|
+
* because `execFile` inherits the parent's env by default. Pin it here,
|
|
78
|
+
* explicitly, to the one project this call is actually allowed to touch:
|
|
79
|
+
* the shadow root passed in as `cwd`. This does not depend on whether the
|
|
80
|
+
* BFF happens to run with `WREN_PROJECT_HOME` unset today.
|
|
81
|
+
*
|
|
82
|
+
* `WREN_HOME` (the `~/.wren` profile/config store, a different variable) is
|
|
83
|
+
* deliberately left ambient rather than pinned: it is a read-only source of
|
|
84
|
+
* real connection credentials, and `dry-run`/`cube query` need the real
|
|
85
|
+
* profile to reach the real data source for the warehouse step — pointing
|
|
86
|
+
* it at a throwaway location would break verification, not sandbox it, and
|
|
87
|
+
* nothing in this ladder writes through it. Checked: `context validate`,
|
|
88
|
+
* `context build`, `dry-plan`, `cube query --sql-only`, and `dry-run` take
|
|
89
|
+
* no other ambient project-selecting input than `WREN_PROJECT_HOME`/cwd/
|
|
90
|
+
* `~/.wren/config.yml`'s `default_project` (per the CLI's own `--path`
|
|
91
|
+
* help text and `discover_project_path()`), and none of the five writes to
|
|
92
|
+
* `WREN_HOME` or any other location outside the project directory it is
|
|
93
|
+
* given.
|
|
94
|
+
*/
|
|
95
|
+
function runWren(args, cwd) {
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
execFile("wren", args, { cwd, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, WREN_PROJECT_HOME: cwd } }, (error, stdout, stderr) => {
|
|
98
|
+
resolve({ stdout, stderr, error: error });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Trims a command's output into something safe and readable to carry in a verdict. */
|
|
103
|
+
function firstMeaningfulLine(...streams) {
|
|
104
|
+
for (const stream of streams) {
|
|
105
|
+
const line = stream
|
|
106
|
+
.split(/\r?\n/)
|
|
107
|
+
.map((entry) => entry.trim())
|
|
108
|
+
.find((entry) => entry.length > 0);
|
|
109
|
+
if (line !== undefined)
|
|
110
|
+
return line.slice(0, 400);
|
|
111
|
+
}
|
|
112
|
+
return "no output";
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A cheap structural read of a YAML draft. Not a YAML parser: it only needs to
|
|
116
|
+
* know which top-level keys are present, and pulling in a parser to answer that
|
|
117
|
+
* would be most of a dependency spent on one question. Matches this package's
|
|
118
|
+
* existing convention of small single-purpose scans (see `server/adopt.ts`).
|
|
119
|
+
*/
|
|
120
|
+
function topLevelKeys(draft) {
|
|
121
|
+
const keys = new Set();
|
|
122
|
+
for (const line of draft.split(/\r?\n/)) {
|
|
123
|
+
const match = /^([A-Za-z_][\w-]*):/.exec(line);
|
|
124
|
+
if (match)
|
|
125
|
+
keys.add(match[1].toLowerCase());
|
|
126
|
+
}
|
|
127
|
+
return keys;
|
|
128
|
+
}
|
|
129
|
+
function checkGrammar(operation) {
|
|
130
|
+
const keys = topLevelKeys(operation.draft);
|
|
131
|
+
if (keys.size === 0)
|
|
132
|
+
return undefined; // not a keyed document (e.g. knowledge markdown)
|
|
133
|
+
const foreign = (FOREIGN_KEYS[operation.changeKind] ?? []).filter((key) => keys.has(key));
|
|
134
|
+
if (foreign.length > 0) {
|
|
135
|
+
return `uses ${foreign.map((key) => `"${key}"`).join(", ")}, which is not Wren's grammar for ${operation.changeKind}; expected ${(REQUIRED_KEYS[operation.changeKind] ?? []).map((key) => `"${key}"`).join(", ")}`;
|
|
136
|
+
}
|
|
137
|
+
const missing = (REQUIRED_KEYS[operation.changeKind] ?? []).filter((key) => !keys.has(key));
|
|
138
|
+
return missing.length > 0 ? `is missing required ${missing.map((key) => `"${key}"`).join(", ")} for ${operation.changeKind}` : undefined;
|
|
139
|
+
}
|
|
140
|
+
/** Measure names declared in a cube draft, for the generated query. */
|
|
141
|
+
function draftMeasureNames(draft) {
|
|
142
|
+
const names = [];
|
|
143
|
+
let inMeasures = false;
|
|
144
|
+
for (const line of draft.split(/\r?\n/)) {
|
|
145
|
+
if (/^measures:\s*$/.test(line)) {
|
|
146
|
+
inMeasures = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (/^[A-Za-z_]/.test(line)) {
|
|
150
|
+
inMeasures = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!inMeasures)
|
|
154
|
+
continue;
|
|
155
|
+
const name = /^\s*-?\s*name:\s*"?([A-Za-z_][\w-]*)"?\s*$/.exec(line)?.[1];
|
|
156
|
+
if (name !== undefined)
|
|
157
|
+
names.push(name);
|
|
158
|
+
}
|
|
159
|
+
return names;
|
|
160
|
+
}
|
|
161
|
+
function probeFor(operation) {
|
|
162
|
+
const name = /^(?:cubes|views|models)\/([a-z0-9_-]+)\//.exec(operation.sink)?.[1];
|
|
163
|
+
if (name === undefined)
|
|
164
|
+
return undefined;
|
|
165
|
+
if (operation.changeKind === "new_cube") {
|
|
166
|
+
const measures = draftMeasureNames(operation.draft);
|
|
167
|
+
return measures.length > 0 ? { kind: "cube", cube: name, measures } : undefined;
|
|
168
|
+
}
|
|
169
|
+
return { kind: "relation", sql: `SELECT * FROM "${name}" LIMIT 0` };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* There is deliberately no opt-out for the data-source step.
|
|
173
|
+
*
|
|
174
|
+
* An earlier version carried an `includeWarehouseDryRun` flag documented as
|
|
175
|
+
* "off by default". Once cubes had to be checked through `dry-run` — nothing
|
|
176
|
+
* local resolves their expressions — the flag stopped being read while its
|
|
177
|
+
* documentation went on promising a round trip could be avoided. An advertised
|
|
178
|
+
* safety lever that does nothing is worse than none, so it is gone.
|
|
179
|
+
*
|
|
180
|
+
* What is true, in one place: verifying a `new_cube` always reaches the data
|
|
181
|
+
* source, via `dry-run`, which parses and validates without returning rows.
|
|
182
|
+
* Every other change kind is verified entirely locally. A caller that cannot
|
|
183
|
+
* reach its source cannot verify a cube, and the honest report for that is a
|
|
184
|
+
* refutation or `unavailable` from the attempt itself — not a flag that skips
|
|
185
|
+
* the check and calls the result verified.
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* Runs the ladder against a shadow copy of the bound project. Stops at the
|
|
189
|
+
* first refutation, so a proposal refuted locally never reaches the warehouse.
|
|
190
|
+
*
|
|
191
|
+
* The verdict carries the proposal hash and project revision it was earned
|
|
192
|
+
* against: a verdict must not survive onto edited content or a moved project.
|
|
193
|
+
*/
|
|
194
|
+
export async function verifyProposal(proposal, binding, options = {}) {
|
|
195
|
+
const run = options.run ?? runWren;
|
|
196
|
+
const bound = { proposalHash: proposal.hash, projectRevision: proposal.projectRevision };
|
|
197
|
+
const stepsRun = [];
|
|
198
|
+
// Steps 1-2 need no project at all, so they run before paying for a copy.
|
|
199
|
+
for (const operation of proposal.operations) {
|
|
200
|
+
const grammar = checkGrammar(operation);
|
|
201
|
+
if (grammar !== undefined) {
|
|
202
|
+
return { status: "refuted", ...bound, stepsRun: ["sink", "grammar"], refutation: { operationId: operation.id, step: "grammar", reason: `draft ${grammar}` } };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
stepsRun.push("sink", "grammar");
|
|
206
|
+
let shadow;
|
|
207
|
+
try {
|
|
208
|
+
shadow = await mkdtemp(path.join(tmpdir(), "wren-enrich-verify-"));
|
|
209
|
+
const project = path.join(shadow, "project");
|
|
210
|
+
// `.env` comes along: the ladder's later steps resolve the project's own
|
|
211
|
+
// connection the way any other wren invocation in that directory would.
|
|
212
|
+
await cp(binding.path, project, { recursive: true });
|
|
213
|
+
for (const operation of proposal.operations) {
|
|
214
|
+
const sinkPath = path.join(project, operation.sink);
|
|
215
|
+
// `canonicalizeProposal` already constrained every sink to a fixed shape,
|
|
216
|
+
// so this cannot escape; assert it anyway, because a path that escapes a
|
|
217
|
+
// temp directory is not a failure worth discovering later.
|
|
218
|
+
if (!path.resolve(sinkPath).startsWith(path.resolve(project) + path.sep)) {
|
|
219
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: operation.id, step: "sink", reason: `sink "${operation.sink}" resolves outside the project` } };
|
|
220
|
+
}
|
|
221
|
+
// The lexical check above never touches the filesystem, so it passes for a
|
|
222
|
+
// path whose components are symlinks. `cp` does not dereference them, so a
|
|
223
|
+
// symlink in the bound project is copied AS a symlink and a later write
|
|
224
|
+
// would follow it out of the shadow — defeating the guarantee this module
|
|
225
|
+
// states. Refuse rather than follow.
|
|
226
|
+
const escaping = await symlinkComponent(project, operation.sink);
|
|
227
|
+
if (escaping !== undefined) {
|
|
228
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: operation.id, step: "sink", reason: `sink "${operation.sink}" passes through a symlink ("${escaping}"), which a write would follow out of the project` } };
|
|
229
|
+
}
|
|
230
|
+
await mkdir(path.dirname(sinkPath), { recursive: true });
|
|
231
|
+
await writeFile(sinkPath, operation.draft.endsWith("\n") ? operation.draft : `${operation.draft}\n`, "utf-8");
|
|
232
|
+
}
|
|
233
|
+
const validate = await run(["context", "validate"], project);
|
|
234
|
+
stepsRun.push("validate");
|
|
235
|
+
if (validate.error !== null) {
|
|
236
|
+
if (validate.error.code === "ENOENT")
|
|
237
|
+
return { status: "unavailable", ...bound, reason: "the wren CLI is not on PATH" };
|
|
238
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: proposal.operations[0]?.id ?? "", step: "validate", reason: firstMeaningfulLine(validate.stderr, validate.stdout) } };
|
|
239
|
+
}
|
|
240
|
+
const build = await run(["context", "build"], project);
|
|
241
|
+
stepsRun.push("build");
|
|
242
|
+
if (build.error !== null) {
|
|
243
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: proposal.operations[0]?.id ?? "", step: "build", reason: firstMeaningfulLine(build.stderr, build.stdout) } };
|
|
244
|
+
}
|
|
245
|
+
for (const operation of proposal.operations) {
|
|
246
|
+
const probe = probeFor(operation);
|
|
247
|
+
if (probe === undefined)
|
|
248
|
+
continue; // nothing queryable (e.g. a knowledge append)
|
|
249
|
+
if (probe.kind === "relation") {
|
|
250
|
+
const plan = await run(["dry-plan", "--sql", probe.sql], project);
|
|
251
|
+
if (!stepsRun.includes("dry_plan"))
|
|
252
|
+
stepsRun.push("dry_plan");
|
|
253
|
+
if (plan.error !== null) {
|
|
254
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: operation.id, step: "dry_plan", reason: firstMeaningfulLine(plan.stderr, plan.stdout) } };
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Cube: generate its query, then have the source parse it. Both steps are
|
|
259
|
+
// needed — generation alone accepts an invented column.
|
|
260
|
+
const generated = await run(["cube", "query", "--cube", probe.cube, "--measures", probe.measures.join(","), "--sql-only"], project);
|
|
261
|
+
if (!stepsRun.includes("dry_plan"))
|
|
262
|
+
stepsRun.push("dry_plan");
|
|
263
|
+
if (generated.error !== null) {
|
|
264
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: operation.id, step: "dry_plan", reason: firstMeaningfulLine(generated.stderr, generated.stdout) } };
|
|
265
|
+
}
|
|
266
|
+
const sql = generated.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).pop();
|
|
267
|
+
if (sql === undefined) {
|
|
268
|
+
return { status: "unavailable", ...bound, reason: `could not generate a query for cube "${probe.cube}"` };
|
|
269
|
+
}
|
|
270
|
+
// Unlike the relation probe, this one reaches the data source: nothing
|
|
271
|
+
// local resolves a cube's expressions, so the alternative is not checking
|
|
272
|
+
// cubes at all. `dry-run` parses and validates without returning rows.
|
|
273
|
+
const dry = await run(["dry-run", "--sql", sql], project);
|
|
274
|
+
if (!stepsRun.includes("dry_run"))
|
|
275
|
+
stepsRun.push("dry_run");
|
|
276
|
+
if (dry.error !== null) {
|
|
277
|
+
return { status: "refuted", ...bound, stepsRun, refutation: { operationId: operation.id, step: "dry_run", reason: firstMeaningfulLine(dry.stderr, dry.stdout) } };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { status: "verified", ...bound, stepsRun };
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
// An unrunnable ladder is reported as such, never as a pass.
|
|
284
|
+
return { status: "unavailable", ...bound, reason: error.message };
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
if (shadow !== undefined)
|
|
288
|
+
await rm(shadow, { recursive: true, force: true }).catch(() => undefined);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export type EnrichmentMode = "grill" | "autopilot";
|
|
2
|
+
export type EnrichmentRisk = "low" | "high" | "conflict" | "ambiguous";
|
|
3
|
+
export type EnrichmentDecision = "accept" | "edit" | "skip";
|
|
4
|
+
export type EnrichmentRunStatus = "drafting" | "awaiting_decision" | "awaiting_approval" | "ready" | "completed" | "cancelled" | "reconcile_required" | "failed";
|
|
5
|
+
/**
|
|
6
|
+
* Deliberately has no kind for "append a description to a model" (a change
|
|
7
|
+
* that targets the same `models/<name>/metadata.yml` sink as `mdl_metric`
|
|
8
|
+
* and `calculated_column`, but is neither). Adding one is not a host-only
|
|
9
|
+
* change: `apply_enrichment` (the warble component that applies an approved
|
|
10
|
+
* operation) already has to know a distinct per-`changeKind` merge shape for
|
|
11
|
+
* every kind that shares that sink, and a description append would be a
|
|
12
|
+
* third, different shape into the same file. That component's guardrails
|
|
13
|
+
* are locked and out of scope here, so until it grows the case, a
|
|
14
|
+
* model-description append has no valid `changeKind` and is refused by
|
|
15
|
+
* `canonicalizeProposal` like any other invalid draft — not silently
|
|
16
|
+
* reclassified as `mdl_metric`/`calculated_column`, which would misroute it.
|
|
17
|
+
*/
|
|
18
|
+
export type EnrichmentChangeKind = "knowledge_append" | "new_cube" | "new_view" | "new_relationship" | "mdl_metric" | "calculated_column" | "conflict" | "ambiguous";
|
|
19
|
+
export type EnrichmentOperationState = "awaiting_decision" | "awaiting_approval" | "ready" | "ready_to_reapply" | "applying" | "applied" | "skipped" | "reconcile_required";
|
|
20
|
+
/**
|
|
21
|
+
* Model-authored confidence is display-only. It never participates in risk,
|
|
22
|
+
* approval, or Auto-pilot eligibility. Numbers remain accepted internally so
|
|
23
|
+
* existing ledgers and callers can be read during the transition to labels.
|
|
24
|
+
*/
|
|
25
|
+
export type EnrichmentConfidence = string | number;
|
|
26
|
+
/** Safe, sink-scoped proposal metadata. Never carry source material or SDK ids. */
|
|
27
|
+
export interface EnrichmentOperation {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly sink: string;
|
|
30
|
+
readonly risk: EnrichmentRisk;
|
|
31
|
+
readonly summary: string;
|
|
32
|
+
readonly confidence: EnrichmentConfidence;
|
|
33
|
+
readonly changeKind: EnrichmentChangeKind;
|
|
34
|
+
readonly draft: string;
|
|
35
|
+
}
|
|
36
|
+
export interface EnrichmentProposal {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly hash: string;
|
|
39
|
+
readonly projectRevision: string;
|
|
40
|
+
readonly operations: readonly EnrichmentOperation[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The host's view of the currently bound project. Paths alone are not an
|
|
44
|
+
* authority: a symlink can be retargeted and the same spelling can name a
|
|
45
|
+
* different directory. `identity` is deliberately a portable textual form of
|
|
46
|
+
* the platform's device/inode pair, while `path` is the canonical realpath.
|
|
47
|
+
*/
|
|
48
|
+
export interface EnrichmentBinding {
|
|
49
|
+
readonly path: string;
|
|
50
|
+
readonly identity: string;
|
|
51
|
+
readonly generation: number;
|
|
52
|
+
readonly revision: string;
|
|
53
|
+
}
|
|
54
|
+
export type UnversionedEnrichmentBinding = Omit<EnrichmentBinding, "generation">;
|
|
55
|
+
/**
|
|
56
|
+
* The subset of a binding that identifies *which directory* is bound,
|
|
57
|
+
* independent of whether it has ever been built. Binding a project (Setup's
|
|
58
|
+
* connect step, adopt, the context step's temporary healthcheck bind) is a
|
|
59
|
+
* foundation operation that must succeed regardless of build state, so it
|
|
60
|
+
* may only ever produce this — never a revision.
|
|
61
|
+
*/
|
|
62
|
+
export type ProjectIdentity = Omit<UnversionedEnrichmentBinding, "revision">;
|
|
63
|
+
/** Untrusted runtime draft: host ignores its ids, hash, revision and risk. */
|
|
64
|
+
export interface EnrichmentDraftOperation {
|
|
65
|
+
readonly sink: string;
|
|
66
|
+
readonly changeKind: EnrichmentChangeKind;
|
|
67
|
+
readonly summary: string;
|
|
68
|
+
readonly draft: string;
|
|
69
|
+
readonly confidence?: unknown;
|
|
70
|
+
readonly id?: string;
|
|
71
|
+
readonly risk?: EnrichmentRisk;
|
|
72
|
+
}
|
|
73
|
+
export interface EnrichmentDraft {
|
|
74
|
+
readonly operations: readonly EnrichmentDraftOperation[];
|
|
75
|
+
readonly id?: string;
|
|
76
|
+
readonly hash?: string;
|
|
77
|
+
readonly projectRevision?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A runner's own live answer to "can you serve `draft()` right now, and if
|
|
81
|
+
* not, why not". `reason` is a short, stable, machine-readable code (never
|
|
82
|
+
* free-form prose): it must already be safe to hand to a browser as-is, so
|
|
83
|
+
* it must never carry a runner/dispatcher path, an SDK session id, or any
|
|
84
|
+
* provider identity beyond what the caller's own runtime configuration
|
|
85
|
+
* already chose. The host route (`server/app.ts`) only forwards whatever
|
|
86
|
+
* `readiness()` returns; it never re-derives it from the runtime config
|
|
87
|
+
* itself, so a runner that grows a new refusal reason surfaces it here
|
|
88
|
+
* without the route needing to change.
|
|
89
|
+
*/
|
|
90
|
+
export interface EnrichmentRunnerReadiness {
|
|
91
|
+
readonly available: boolean;
|
|
92
|
+
readonly reason?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface EnrichmentRunner {
|
|
95
|
+
/**
|
|
96
|
+
* A runner only drafts a typed proposal. It must not mutate a project: the
|
|
97
|
+
* host validates and applies accepted operations through its completion
|
|
98
|
+
* ledger. The production constructor deliberately does not provide one
|
|
99
|
+
* until the dispatcher exposes this callback contract.
|
|
100
|
+
*/
|
|
101
|
+
draft(input: {
|
|
102
|
+
readonly projectPath: string;
|
|
103
|
+
readonly mode: EnrichmentMode;
|
|
104
|
+
readonly projectRevision: string;
|
|
105
|
+
}): Promise<unknown>;
|
|
106
|
+
/**
|
|
107
|
+
* Optional live readiness check, independent of `draft()`: it must return
|
|
108
|
+
* without dispatching, without a live model turn, and without any other
|
|
109
|
+
* cost or side effect -- a pure read of whatever the runner already
|
|
110
|
+
* consults to decide whether to refuse (e.g. the current `AuthChoice`).
|
|
111
|
+
* Omitted only by test doubles that don't model refusal; such a runner is
|
|
112
|
+
* treated as always available, matching this capability's behavior before
|
|
113
|
+
* `readiness()` existed.
|
|
114
|
+
*/
|
|
115
|
+
readiness?(): EnrichmentRunnerReadiness;
|
|
116
|
+
}
|
|
117
|
+
/** Trusted deterministic apply seam owned and invoked by the host. */
|
|
118
|
+
export interface EnrichmentApplyRunner {
|
|
119
|
+
apply(input: {
|
|
120
|
+
readonly projectPath: string;
|
|
121
|
+
readonly projectRevision: string;
|
|
122
|
+
readonly proposalHash: string;
|
|
123
|
+
readonly operation: EnrichmentOperation;
|
|
124
|
+
readonly idempotencyKey: string;
|
|
125
|
+
readonly fence: number;
|
|
126
|
+
}): Promise<{
|
|
127
|
+
readonly validationDigest: string;
|
|
128
|
+
readonly buildDigest: string;
|
|
129
|
+
}>;
|
|
130
|
+
reconcile(input: {
|
|
131
|
+
readonly idempotencyKey: string;
|
|
132
|
+
readonly fence: number;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
readonly state: "applied" | "not_applied" | "still_unknown";
|
|
135
|
+
readonly validationDigest?: string;
|
|
136
|
+
readonly buildDigest?: string;
|
|
137
|
+
}>;
|
|
138
|
+
}
|
|
139
|
+
/** The host never sends proposal text to an approval provider. */
|
|
140
|
+
export interface EnrichmentApprovalRequest {
|
|
141
|
+
readonly runId: string;
|
|
142
|
+
readonly binding: EnrichmentBinding;
|
|
143
|
+
readonly proposalHash: string;
|
|
144
|
+
readonly operation: {
|
|
145
|
+
readonly id: string;
|
|
146
|
+
readonly sink: string;
|
|
147
|
+
readonly risk: EnrichmentRisk;
|
|
148
|
+
readonly changeKind: EnrichmentChangeKind;
|
|
149
|
+
readonly hash: string;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** A trusted host callback returns an opaque, short-lived authorization record. */
|
|
153
|
+
export interface EnrichmentApprovalAttestation {
|
|
154
|
+
readonly evidenceRef: string;
|
|
155
|
+
readonly nonce: string;
|
|
156
|
+
readonly expiresAt: string;
|
|
157
|
+
readonly binding: EnrichmentBinding;
|
|
158
|
+
readonly proposalHash: string;
|
|
159
|
+
readonly operationHash: string;
|
|
160
|
+
}
|
|
161
|
+
export interface EnrichmentApprovalProvider {
|
|
162
|
+
attest(input: EnrichmentApprovalRequest): Promise<EnrichmentApprovalAttestation>;
|
|
163
|
+
}
|
|
164
|
+
export declare class EnrichmentContractError extends Error {
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Identifies a project directory without requiring it to be built. This is
|
|
168
|
+
* the only resolution `bindProject` may use: binding must succeed for a
|
|
169
|
+
* freshly connected, not-yet-compiled project, so it can never read or
|
|
170
|
+
* require `target/mdl.json`.
|
|
171
|
+
*/
|
|
172
|
+
export declare function resolveProjectIdentity(projectPath: string): ProjectIdentity;
|
|
173
|
+
/**
|
|
174
|
+
* Identifies a project directory *and* proves it is built. Only enrichment
|
|
175
|
+
* call sites may use this: they need a concrete revision to lock a run
|
|
176
|
+
* against or to fence an in-flight operation, and it is legitimate for them
|
|
177
|
+
* to refuse (via `EnrichmentContractError`) when the project isn't built.
|
|
178
|
+
*/
|
|
179
|
+
export declare function resolveEnrichmentBinding(projectPath: string): UnversionedEnrichmentBinding;
|
|
180
|
+
export declare function sameEnrichmentBinding(left: EnrichmentBinding, right: EnrichmentBinding): boolean;
|
|
181
|
+
export declare function computeBoundProjectRevision(projectPath: string): string;
|
|
182
|
+
/**
|
|
183
|
+
* Converts untrusted model confidence into bounded UI text without making it
|
|
184
|
+
* an operation-validity gate. Unsafe, missing, or structured values become a
|
|
185
|
+
* neutral label instead of costing an otherwise valid proposal.
|
|
186
|
+
*/
|
|
187
|
+
export declare function normalizeEnrichmentConfidence(value: unknown): string;
|
|
188
|
+
export declare function hashEnrichmentOperation(operation: Pick<EnrichmentOperation, "id" | "sink" | "risk" | "summary" | "draft" | "changeKind">): string;
|
|
189
|
+
export declare function canonicalizeProposal(draft: unknown, revision: string): EnrichmentProposal;
|
|
190
|
+
export declare function requiresApproval(mode: EnrichmentMode, risk: EnrichmentRisk): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* The host-mediated MCP tool a native `context_enrichment` session uses to
|
|
193
|
+
* hand a drafted proposal to the host. The agent never writes into the bound
|
|
194
|
+
* project directly: this tool only carries the proposal *document* through
|
|
195
|
+
* to the same canonicalize -> shadow-verify -> finalize pipeline `POST
|
|
196
|
+
* /api/context/enrichment/start` already uses (see `server/app.ts`'s
|
|
197
|
+
* `settleEnrichmentDraft`). The schema below is deliberately permissive --
|
|
198
|
+
* `canonicalizeProposal` above is the single source of truth for shape and
|
|
199
|
+
* content validation, so this schema must not duplicate or drift from it.
|
|
200
|
+
*/
|
|
201
|
+
export declare const NATIVE_ENRICHMENT_SUBMIT_MCP_TOOL_NAME = "submit_context_proposal";
|
|
202
|
+
export declare const NATIVE_ENRICHMENT_SUBMIT_INPUT_SCHEMA: {
|
|
203
|
+
readonly type: "object";
|
|
204
|
+
readonly additionalProperties: false;
|
|
205
|
+
readonly required: readonly ["operations"];
|
|
206
|
+
readonly properties: {
|
|
207
|
+
readonly operations: {
|
|
208
|
+
readonly type: "array";
|
|
209
|
+
readonly minItems: 1;
|
|
210
|
+
readonly items: {
|
|
211
|
+
readonly type: "object";
|
|
212
|
+
readonly additionalProperties: false;
|
|
213
|
+
readonly required: readonly ["sink", "changeKind", "summary", "draft"];
|
|
214
|
+
readonly properties: {
|
|
215
|
+
readonly sink: {
|
|
216
|
+
readonly type: "string";
|
|
217
|
+
};
|
|
218
|
+
readonly changeKind: {
|
|
219
|
+
readonly enum: readonly ["knowledge_append", "new_cube", "new_view", "new_relationship", "mdl_metric", "calculated_column", "conflict", "ambiguous"];
|
|
220
|
+
};
|
|
221
|
+
readonly summary: {
|
|
222
|
+
readonly type: "string";
|
|
223
|
+
};
|
|
224
|
+
readonly draft: {
|
|
225
|
+
readonly type: "string";
|
|
226
|
+
};
|
|
227
|
+
readonly confidence: {};
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
};
|