@wrenai/genbi 0.0.4 → 0.0.5
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/README.md +64 -2
- package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
- package/dist/assets/index-A_Wrq7Ey.css +1 -0
- package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
- package/dist/index.html +2 -2
- package/dist-server/harness/bundle/schema.d.ts +38 -0
- package/dist-server/harness/bundle/schema.js +18 -10
- package/dist-server/harness/bundle/version.js +1 -1
- package/dist-server/harness/cli-args.d.ts +10 -2
- package/dist-server/harness/cli-args.js +24 -8
- package/dist-server/harness/cli.js +75 -9
- package/dist-server/harness/compile/cache.js +4 -4
- package/dist-server/harness/compile/compose-profile.d.ts +51 -14
- package/dist-server/harness/compile/compose-profile.js +85 -19
- package/dist-server/harness/compile/context-loader.d.ts +56 -0
- package/dist-server/harness/compile/context-loader.js +237 -0
- package/dist-server/harness/compile/errors.d.ts +19 -0
- package/dist-server/harness/compile/errors.js +35 -0
- package/dist-server/harness/compile/index.d.ts +4 -3
- package/dist-server/harness/compile/index.js +4 -3
- package/dist-server/harness/compile/pipeline.d.ts +15 -7
- package/dist-server/harness/compile/pipeline.js +62 -14
- package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
- package/dist-server/harness/compile/resolve-binary.js +3 -3
- package/dist-server/harness/compile/types.d.ts +29 -0
- package/dist-server/harness/compile/warble-identity.d.ts +11 -0
- package/dist-server/harness/compile/warble-identity.js +13 -0
- package/dist-server/harness/components/ai-step.d.ts +4 -0
- package/dist-server/harness/components/ai-step.js +40 -0
- package/dist-server/harness/components/broker.d.ts +60 -0
- package/dist-server/harness/components/broker.js +117 -0
- package/dist-server/harness/components/capability-card.d.ts +98 -0
- package/dist-server/harness/components/capability-card.js +169 -0
- package/dist-server/harness/components/context.d.ts +2 -0
- package/dist-server/harness/components/context.js +30 -0
- package/dist-server/harness/components/display.d.ts +6 -0
- package/dist-server/harness/components/display.js +29 -0
- package/dist-server/harness/components/egress-judge.d.ts +12 -0
- package/dist-server/harness/components/egress-judge.js +27 -0
- package/dist-server/harness/components/egress.d.ts +142 -0
- package/dist-server/harness/components/egress.js +469 -0
- package/dist-server/harness/components/in-process.d.ts +7 -0
- package/dist-server/harness/components/in-process.js +245 -0
- package/dist-server/harness/components/normalize.d.ts +35 -0
- package/dist-server/harness/components/normalize.js +245 -0
- package/dist-server/harness/components/plan.d.ts +31 -0
- package/dist-server/harness/components/plan.js +184 -0
- package/dist-server/harness/components/report.d.ts +207 -0
- package/dist-server/harness/components/report.js +309 -0
- package/dist-server/harness/components/runner.d.ts +193 -0
- package/dist-server/harness/components/runner.js +492 -0
- package/dist-server/harness/components/semantic-guards.d.ts +20 -0
- package/dist-server/harness/components/semantic-guards.js +42 -0
- package/dist-server/harness/components/terminal-json.d.ts +18 -0
- package/dist-server/harness/components/terminal-json.js +82 -0
- package/dist-server/harness/components/usage.d.ts +14 -0
- package/dist-server/harness/components/usage.js +19 -0
- package/dist-server/harness/components/wren-access.d.ts +29 -0
- package/dist-server/harness/components/wren-access.js +233 -0
- package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
- package/dist-server/harness/components/zone-dry-run.js +107 -0
- package/dist-server/harness/components/zone-gate.d.ts +76 -0
- package/dist-server/harness/components/zone-gate.js +144 -0
- package/dist-server/harness/events/index.d.ts +1 -1
- package/dist-server/harness/events/types.d.ts +52 -0
- package/dist-server/harness/index.d.ts +9 -2
- package/dist-server/harness/index.js +5 -1
- package/dist-server/harness/loop/executor.d.ts +1 -0
- package/dist-server/harness/loop/executor.js +37 -15
- package/dist-server/harness/npm-package-resolve.d.ts +2 -0
- package/dist-server/harness/npm-package-resolve.js +16 -4
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
- package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
- package/dist-server/harness/providers/adapters/openai.js +6 -0
- package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
- package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
- package/dist-server/harness/providers/binding.d.ts +45 -4
- package/dist-server/harness/providers/binding.js +44 -8
- package/dist-server/harness/providers/index.d.ts +9 -3
- package/dist-server/harness/providers/index.js +10 -3
- package/dist-server/harness/providers/zone.d.ts +96 -0
- package/dist-server/harness/providers/zone.js +112 -0
- package/dist-server/harness/render/answering-query.d.ts +29 -0
- package/dist-server/harness/render/answering-query.js +62 -0
- package/dist-server/harness/render/envelope.d.ts +6 -0
- package/dist-server/harness/render/envelope.js +10 -3
- package/dist-server/harness/route/adapter-spec.d.ts +8 -9
- package/dist-server/harness/route/adapter-spec.js +13 -14
- package/dist-server/harness/route/codex-ask-events.js +2 -1
- package/dist-server/harness/route/codex-ask.d.ts +4 -0
- package/dist-server/harness/route/codex-ask.js +69 -30
- package/dist-server/harness/route/codex-host-query.d.ts +52 -0
- package/dist-server/harness/route/codex-host-query.js +191 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
- package/dist-server/harness/route/codex-local-manifest.js +96 -15
- package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
- package/dist-server/harness/route/codex-wren-proxy.js +227 -0
- package/dist-server/harness/route/describe.js +45 -6
- package/dist-server/harness/route/in-process.d.ts +22 -1
- package/dist-server/harness/route/in-process.js +54 -4
- package/dist-server/harness/route/route.js +5 -2
- package/dist-server/harness/route/types.d.ts +30 -1
- package/dist-server/harness/session/run.js +30 -10
- package/dist-server/harness/setup/runner.d.ts +2 -0
- package/dist-server/harness/setup/runner.js +38 -17
- package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
- package/dist-server/harness/setup/yaml-scalar.js +62 -0
- package/dist-server/server/adopt.js +1 -17
- package/dist-server/server/app.d.ts +1 -0
- package/dist-server/server/app.js +163 -14
- package/dist-server/server/auth-choice.d.ts +13 -1
- package/dist-server/server/auth-choice.js +66 -1
- package/dist-server/server/bin.js +177 -25
- package/dist-server/server/codex-local-grounding.d.ts +18 -0
- package/dist-server/server/codex-local-grounding.js +92 -0
- package/dist-server/server/codex-native-session.d.ts +25 -0
- package/dist-server/server/codex-native-session.js +1 -0
- package/dist-server/server/codex-runtime-composition.d.ts +34 -0
- package/dist-server/server/codex-runtime-composition.js +250 -0
- package/dist-server/server/codex-structured-ask.d.ts +23 -0
- package/dist-server/server/codex-structured-ask.js +102 -0
- package/dist-server/server/conn-config.js +4 -16
- package/dist-server/server/conversation-socket.d.ts +16 -0
- package/dist-server/server/conversation-socket.js +65 -0
- package/dist-server/server/db.d.ts +46 -0
- package/dist-server/server/db.js +88 -9
- package/dist-server/server/env-detect.d.ts +6 -0
- package/dist-server/server/env-detect.js +7 -0
- package/dist-server/server/fold.d.ts +22 -4
- package/dist-server/server/fold.js +37 -5
- package/dist-server/server/harness.d.ts +2 -2
- package/dist-server/server/harness.js +33 -7
- package/dist-server/server/interactive-terminal.d.ts +10 -6
- package/dist-server/server/interactive-terminal.js +33 -8
- package/dist-server/server/managed-wren-runtime.d.ts +116 -0
- package/dist-server/server/managed-wren-runtime.js +670 -0
- package/dist-server/server/native-artifacts.d.ts +23 -7
- package/dist-server/server/native-artifacts.js +43 -12
- package/dist-server/server/native-component-context.d.ts +19 -0
- package/dist-server/server/native-component-context.js +76 -0
- package/dist-server/server/native-component-preparation.d.ts +20 -0
- package/dist-server/server/native-component-preparation.js +129 -0
- package/dist-server/server/native-component-runtime.d.ts +36 -0
- package/dist-server/server/native-component-runtime.js +92 -0
- package/dist-server/server/native-components.d.ts +84 -0
- package/dist-server/server/native-components.js +160 -0
- package/dist-server/server/native-dispatch-process.d.ts +8 -0
- package/dist-server/server/native-dispatch-process.js +65 -0
- package/dist-server/server/native-runtime-spec.d.ts +89 -0
- package/dist-server/server/native-runtime-spec.js +169 -0
- package/dist-server/server/native-sessions.d.ts +172 -19
- package/dist-server/server/native-sessions.js +839 -244
- package/dist-server/server/native-shutdown.d.ts +6 -0
- package/dist-server/server/native-shutdown.js +28 -0
- package/dist-server/server/native-wren-home.d.ts +18 -0
- package/dist-server/server/native-wren-home.js +262 -0
- package/dist-server/server/node-pty-host.d.ts +2 -1
- package/dist-server/server/node-pty-host.js +3 -2
- package/dist-server/server/profile-registry.d.ts +139 -0
- package/dist-server/server/profile-registry.js +620 -0
- package/dist-server/server/runtime-binding.d.ts +4 -1
- package/dist-server/server/runtime-binding.js +50 -2
- package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
- package/dist-server/server/runtime-host/claude-component-step.js +186 -0
- package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
- package/dist-server/server/runtime-host/codex-app-server.js +239 -0
- package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
- package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
- package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
- package/dist-server/server/runtime-host/codex-component-step.js +308 -0
- package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
- package/dist-server/server/runtime-host/codex-conversation.js +295 -0
- package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
- package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
- package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
- package/dist-server/server/runtime-host/codex-events.js +66 -0
- package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
- package/dist-server/server/runtime-host/codex-policy.js +151 -0
- package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
- package/dist-server/server/runtime-host/codex-process.js +96 -0
- package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
- package/dist-server/server/runtime-host/codex-rpc.js +182 -0
- package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
- package/dist-server/server/runtime-host/codex-session.js +492 -0
- package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
- package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
- package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
- package/dist-server/server/runtime-host/codex-terminal.js +120 -0
- package/dist-server/server/runtime-host/index.d.ts +5 -0
- package/dist-server/server/runtime-host/index.js +3 -0
- package/dist-server/server/runtime-host/local.d.ts +29 -0
- package/dist-server/server/runtime-host/local.js +90 -0
- package/dist-server/server/runtime-host/policy.d.ts +8 -0
- package/dist-server/server/runtime-host/policy.js +78 -0
- package/dist-server/server/runtime-host/types.d.ts +52 -0
- package/dist-server/server/runtime-host/types.js +62 -0
- package/dist-server/server/structured-query-evidence.d.ts +7 -0
- package/dist-server/server/structured-query-evidence.js +47 -0
- package/dist-server/server/structured-runtime.d.ts +52 -0
- package/dist-server/server/structured-runtime.js +130 -0
- package/dist-server/server/structured-scope.d.ts +4 -0
- package/dist-server/server/structured-scope.js +12 -0
- package/dist-server/server/turn.d.ts +6 -1
- package/dist-server/server/turn.js +82 -25
- package/dist-server/server/wire-types.d.ts +67 -3
- package/dist-server/server/wren-profiles.js +1 -1
- package/managed-wren/README.md +60 -0
- package/managed-wren/dependency-wheels.json +282 -0
- package/managed-wren/manifest.json +373 -0
- package/managed-wren/release-inputs.json +45 -0
- package/managed-wren/runtime-tree.cjs +115 -0
- package/managed-wren/source-inputs.json +40 -0
- package/package.json +15 -6
- package/profiles/genbi-default/context/binding.yml +17 -7
- package/profiles/genbi-default/context/context.json +146 -0
- package/profiles/genbi-default/ir.golden.json +39 -43
- package/profiles/genbi-default/profile.yml +31 -0
- package/profiles/genbi-enrich-context/context/binding.yml +14 -3
- package/profiles/genbi-enrich-context/context/context.json +146 -0
- package/profiles/genbi-enrich-context/ir.golden.json +4 -5
- package/profiles/genbi-monitor/context/binding.yml +13 -5
- package/profiles/genbi-monitor/context/context.json +146 -0
- package/profiles/genbi-monitor/ir.golden.json +13 -7
- package/profiles/genbi-report/context/binding.yml +19 -0
- package/profiles/genbi-report/context/context.json +223 -0
- package/profiles/genbi-report/ir.golden.json +328 -0
- package/profiles/genbi-report/profile.yml +38 -0
- package/profiles/genbi-setup/ir.golden.json +3 -1
- package/dist/assets/index-Xs4q7pNo.css +0 -1
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { answerShapeSchema } from "../providers/zone.js";
|
|
3
|
+
export const slotDeclarationSchema = z.object({
|
|
4
|
+
slot_id: z.string().min(1).max(128),
|
|
5
|
+
/** The planner's layout hint; carried in the Hub `plan_report` call, ignored by verification. */
|
|
6
|
+
block_type: z.string().max(64).optional(),
|
|
7
|
+
expected_shape: answerShapeSchema,
|
|
8
|
+
question: z.string().min(1),
|
|
9
|
+
unit: z.string().optional(),
|
|
10
|
+
max_rows: z.number().int().positive().optional(),
|
|
11
|
+
}).strict();
|
|
12
|
+
const slotsSchema = z.array(slotDeclarationSchema).min(1).max(64)
|
|
13
|
+
.refine((slots) => new Set(slots.map((slot) => slot.slot_id)).size === slots.length, "slot ids must be unique");
|
|
14
|
+
const judgeOutputSchema = z.object({
|
|
15
|
+
verdict: z.enum(["pass", "redact", "refuse"]),
|
|
16
|
+
reason_category: z.string().max(64).optional(),
|
|
17
|
+
redact_columns: z.array(z.string().min(1)).max(256).optional(),
|
|
18
|
+
}).strict();
|
|
19
|
+
const tableSchema = z.object({
|
|
20
|
+
columns: z.array(z.string()).min(1),
|
|
21
|
+
rows: z.array(z.unknown()),
|
|
22
|
+
summary: z.string().optional(),
|
|
23
|
+
unit: z.string().optional(),
|
|
24
|
+
text: z.string().optional(),
|
|
25
|
+
definition: z.unknown().optional(),
|
|
26
|
+
verified: z.boolean().optional(),
|
|
27
|
+
slot_id: z.string().optional(),
|
|
28
|
+
}).passthrough();
|
|
29
|
+
const narrativeSchema = z.object({ text: z.string().min(1), slot_id: z.string().optional(), definition: z.unknown().optional(), unit: z.string().optional(), verified: z.boolean().optional() }).passthrough();
|
|
30
|
+
/** A per-slot entry the callee declined itself: `{slot_id, status: "unanswerable" | "refused", reason?}`. */
|
|
31
|
+
const unavailableEntrySchema = z.object({ slot_id: z.string().min(1), status: z.enum(["unanswerable", "refused"]) }).passthrough();
|
|
32
|
+
/** A batch is an array of per-slot entries, or `{answers: [...]}`; entries are classified one by one, so an unanswerable entry does not invalidate its neighbours. */
|
|
33
|
+
const batchSchema = z.union([
|
|
34
|
+
z.array(z.unknown()),
|
|
35
|
+
z.object({ answers: z.array(z.unknown()) }).passthrough(),
|
|
36
|
+
]);
|
|
37
|
+
const PAYMENT_CARD = /\b(?:\d[ -]?){13,19}\b/;
|
|
38
|
+
const PHONE_NUMBER = /(?:\+?\d{1,3}[ -]?)?\(?\d{3}\)?[ -]?\d{3}[ -]?\d{4}\b/;
|
|
39
|
+
/** Built-in PII patterns applied to every string cell, in addition to the policy's own. */
|
|
40
|
+
export const BUILT_IN_PII_PATTERNS = [
|
|
41
|
+
/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/, // email
|
|
42
|
+
/\b\d{3}-\d{2}-\d{4}\b/, // US SSN
|
|
43
|
+
PAYMENT_CARD,
|
|
44
|
+
PHONE_NUMBER,
|
|
45
|
+
/\b(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}\b/, // IPv4
|
|
46
|
+
/\+[1-9]\d{0,2}\.\d{6,14}\b/, // dotted international phone
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* The card and phone patterns match bare digit runs, so the fraction of a decimal (`0.12345678901234`)
|
|
50
|
+
* looks like one. They alone run on the text with each decimal cut to its integer part; every other
|
|
51
|
+
* pattern, the policy's own included, runs on the raw text.
|
|
52
|
+
*/
|
|
53
|
+
const DIGIT_RUN_PATTERNS = new Set([PAYMENT_CARD, PHONE_NUMBER]);
|
|
54
|
+
/** A run of dot-separated digit groups. Only a two-group run is a decimal; three or more (an IPv4 address, a version) stays as written. */
|
|
55
|
+
const DOTTED_DIGITS = /\d+(?:\.\d+)+/g;
|
|
56
|
+
function withoutFractions(text) {
|
|
57
|
+
return text.replace(DOTTED_DIGITS, (run) => {
|
|
58
|
+
const groups = run.split(".");
|
|
59
|
+
return groups.length === 2 ? groups[0] : run;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function piiPatterns(policy) {
|
|
63
|
+
return [...BUILT_IN_PII_PATTERNS, ...policy.pii_patterns.map(compile)];
|
|
64
|
+
}
|
|
65
|
+
function hasPii(text, patterns) {
|
|
66
|
+
const integral = withoutFractions(text);
|
|
67
|
+
return patterns.some((pattern) => pattern.test(DIGIT_RUN_PATTERNS.has(pattern) ? integral : text));
|
|
68
|
+
}
|
|
69
|
+
// Person-name columns count as identifiers: a row keyed by a person's name describes one person.
|
|
70
|
+
// A bare `name` does not, so `product_name` and `category_name` stay grouping keys.
|
|
71
|
+
const IDENTIFIER_LIKE = /(^|[_\s-])(id|ids|uuid|guid|key|email|e_mail|phone|ssn|account|user|customer|employee|person|member|(?:first|last|full|given|family)[_\s-]?name|surname)([_\s-]|$)/i;
|
|
72
|
+
const COUNT_LIKE = /^(count|cnt|n|num|n_[a-z_]+|[a-z_]+_count|number_of_[a-z_]+|num_[a-z_]+|count_[a-z_]+|cnt_[a-z_]+|[a-z_]+_cnt|group_size|size)$/i;
|
|
73
|
+
function compile(pattern) {
|
|
74
|
+
try {
|
|
75
|
+
return new RegExp(pattern, "i");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function cellStrings(rows) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const row of rows) {
|
|
84
|
+
const values = Array.isArray(row) ? row : row && typeof row === "object" ? Object.values(row) : [row];
|
|
85
|
+
for (const value of values)
|
|
86
|
+
if (typeof value === "string")
|
|
87
|
+
out.push(value);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function columnValues(table, column) {
|
|
92
|
+
const index = table.columns.indexOf(column);
|
|
93
|
+
return table.rows.map((row) => Array.isArray(row) ? row[index] : row && typeof row === "object" ? row[column] : undefined);
|
|
94
|
+
}
|
|
95
|
+
function actualShapeFits(expected, table) {
|
|
96
|
+
switch (expected) {
|
|
97
|
+
case "scalar": return table.rows.length === 1 && table.columns.length === 1;
|
|
98
|
+
case "series": return table.columns.length >= 2;
|
|
99
|
+
case "table": return true;
|
|
100
|
+
// A batch callee answers a narrative slot with the rows it grounded the prose in plus the prose
|
|
101
|
+
// as `summary`; the prose is what crosses (see the table branch of `verifyEgress`).
|
|
102
|
+
case "narrative": return typeof table.summary === "string" && table.summary.trim().length > 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function identifierLikeKeys(table) {
|
|
106
|
+
return table.columns.filter((column) => IDENTIFIER_LIKE.test(column) && columnValues(table, column).some((value) => typeof value !== "number"));
|
|
107
|
+
}
|
|
108
|
+
function groupCount(table) {
|
|
109
|
+
const counts = table.columns.filter((column) => COUNT_LIKE.test(column));
|
|
110
|
+
return counts.length > 0 ? table.rows.length : table.rows.length;
|
|
111
|
+
}
|
|
112
|
+
/** Smallest group size the answer itself reports, or undefined when it carries no group metadata. */
|
|
113
|
+
function smallestGroup(table) {
|
|
114
|
+
const counts = table.columns.filter((column) => COUNT_LIKE.test(column));
|
|
115
|
+
const values = counts.flatMap((column) => columnValues(table, column)).filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
116
|
+
return values.length ? Math.min(...values) : undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The free text the callee's model wrote on an entry. It is model output, not data: it is scanned
|
|
120
|
+
* like the cells before the judge, and after a non-pass verdict none of it crosses (see `redactColumns`).
|
|
121
|
+
* Keys outside the declared entry shape are never scanned because they never cross: every disclosed
|
|
122
|
+
* answer is rebuilt field by field.
|
|
123
|
+
*/
|
|
124
|
+
function modelText(answer) {
|
|
125
|
+
return [answer.summary, answer.text, answer.unit].filter((value) => typeof value === "string");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The policy's sensitive-column patterns and the PII patterns, applied to free text. In prose a
|
|
129
|
+
* column pattern must match as a whole word ("phone" hits "contact phone", not "iPhone"); column
|
|
130
|
+
* names are still matched exactly as written.
|
|
131
|
+
*/
|
|
132
|
+
function textReason(texts, policy) {
|
|
133
|
+
const sensitive = policy.sensitive_column_patterns.map(compile).map((pattern) => new RegExp(`\\b(?:${pattern.source})\\b`, "i"));
|
|
134
|
+
if (texts.some((text) => sensitive.some((pattern) => pattern.test(text))))
|
|
135
|
+
return "sensitive_column";
|
|
136
|
+
const pii = piiPatterns(policy);
|
|
137
|
+
if (texts.some((text) => hasPii(text, pii)))
|
|
138
|
+
return "pii_pattern";
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
function deterministic(slot, table, policy) {
|
|
142
|
+
if (!policy.allowed_shapes.includes(slot.expected_shape) || !actualShapeFits(slot.expected_shape, table))
|
|
143
|
+
return "shape_mismatch";
|
|
144
|
+
const limit = Math.min(policy.max_rows, slot.max_rows ?? Number.POSITIVE_INFINITY);
|
|
145
|
+
if (table.rows.length > limit)
|
|
146
|
+
return "row_limit";
|
|
147
|
+
const sensitive = policy.sensitive_column_patterns.map(compile);
|
|
148
|
+
if (table.columns.some((column) => sensitive.some((pattern) => pattern.test(column))))
|
|
149
|
+
return "sensitive_column";
|
|
150
|
+
const pii = piiPatterns(policy);
|
|
151
|
+
if (cellStrings(table.rows).some((cell) => hasPii(cell, pii)))
|
|
152
|
+
return "pii_pattern";
|
|
153
|
+
const inText = textReason(modelText(table), policy);
|
|
154
|
+
if (inText)
|
|
155
|
+
return inText;
|
|
156
|
+
if (policy.min_group_size > 1 && table.rows.length > 0) {
|
|
157
|
+
if (identifierLikeKeys(table).length > 0)
|
|
158
|
+
return "group_size";
|
|
159
|
+
const smallest = smallestGroup(table);
|
|
160
|
+
if (smallest !== undefined && smallest < policy.min_group_size)
|
|
161
|
+
return "group_size";
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function deterministicNarrative(slot, text, policy, unit) {
|
|
166
|
+
if (slot.expected_shape !== "narrative" || !policy.allowed_shapes.includes("narrative"))
|
|
167
|
+
return "shape_mismatch";
|
|
168
|
+
return textReason(unit !== undefined ? [text, unit] : [text], policy);
|
|
169
|
+
}
|
|
170
|
+
function extractJsonObject(text) {
|
|
171
|
+
const start = text.indexOf("{");
|
|
172
|
+
const end = text.lastIndexOf("}");
|
|
173
|
+
if (start < 0 || end <= start)
|
|
174
|
+
throw new Error("no JSON object");
|
|
175
|
+
return JSON.parse(text.slice(start, end + 1));
|
|
176
|
+
}
|
|
177
|
+
async function consultJudge(judge, input, policy, parent) {
|
|
178
|
+
if (!judge)
|
|
179
|
+
return { verdict: "refuse", reason: "judge_unavailable" };
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
const timer = setTimeout(() => controller.abort(), policy.judge_timeout_ms);
|
|
182
|
+
const abort = () => controller.abort();
|
|
183
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
184
|
+
if (parent?.aborted)
|
|
185
|
+
controller.abort();
|
|
186
|
+
try {
|
|
187
|
+
const raw = await Promise.race([
|
|
188
|
+
judge(input, controller.signal),
|
|
189
|
+
new Promise((_, reject) => controller.signal.addEventListener("abort", () => reject(new Error("judge timed out")), { once: true })),
|
|
190
|
+
]);
|
|
191
|
+
const value = typeof raw === "string" ? extractJsonObject(raw) : raw;
|
|
192
|
+
const parsed = judgeOutputSchema.safeParse(value);
|
|
193
|
+
if (!parsed.success)
|
|
194
|
+
return { verdict: "refuse", reason: "judge_invalid" };
|
|
195
|
+
if (parsed.data.verdict === "refuse")
|
|
196
|
+
return { verdict: "refuse", reason: "judge_refused" };
|
|
197
|
+
if (parsed.data.verdict === "redact") {
|
|
198
|
+
if (!parsed.data.redact_columns?.length)
|
|
199
|
+
return { verdict: "refuse", reason: "judge_invalid" };
|
|
200
|
+
return { verdict: "redact", redact_columns: parsed.data.redact_columns };
|
|
201
|
+
}
|
|
202
|
+
return { verdict: "pass" };
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return { verdict: "refuse", reason: controller.signal.aborted ? "judge_unavailable" : "judge_invalid" };
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
clearTimeout(timer);
|
|
209
|
+
parent?.removeEventListener("abort", abort);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** A host-written, value-free description of a redacted table: the row count and the surviving column names. */
|
|
213
|
+
function neutralSummary(columns, rowCount) {
|
|
214
|
+
return `${rowCount} ${rowCount === 1 ? "row" : "rows"}; columns: ${columns.join(", ")}.`;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Drops the judge's columns and rebuilds the entry from the surviving columns and rows alone. The
|
|
218
|
+
* callee's `summary`, `text`, `unit` and every other field are model-written or undeclared and are
|
|
219
|
+
* not carried, because the redacted values may be named in them; `summary` becomes {@link neutralSummary}.
|
|
220
|
+
*/
|
|
221
|
+
function isPlainObject(value) {
|
|
222
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
223
|
+
return false;
|
|
224
|
+
const prototype = Object.getPrototypeOf(value);
|
|
225
|
+
return prototype === Object.prototype || prototype === null;
|
|
226
|
+
}
|
|
227
|
+
function redactColumns(table, columns) {
|
|
228
|
+
const drop = new Set(columns);
|
|
229
|
+
if (columns.some((column) => !table.columns.includes(column)))
|
|
230
|
+
return undefined;
|
|
231
|
+
const keep = table.columns.filter((column) => !drop.has(column));
|
|
232
|
+
if (keep.length === 0)
|
|
233
|
+
return undefined;
|
|
234
|
+
// A row that is neither positional nor keyed cannot be projected, so the redaction cannot be shown to hold.
|
|
235
|
+
if (!table.rows.every((row) => Array.isArray(row) || isPlainObject(row)))
|
|
236
|
+
return undefined;
|
|
237
|
+
const rows = table.rows.map((row) => Array.isArray(row)
|
|
238
|
+
? keep.map((column) => row[table.columns.indexOf(column)])
|
|
239
|
+
: Object.fromEntries(keep.map((column) => [column, row[column]])));
|
|
240
|
+
return { columns: keep, rows, summary: neutralSummary(keep, rows.length) };
|
|
241
|
+
}
|
|
242
|
+
/** Redacted string values shorter than this would match almost any text, so they are not searched for. */
|
|
243
|
+
const LEAK_MIN_LENGTH = 3;
|
|
244
|
+
/**
|
|
245
|
+
* Post-redaction consistency: no string value of a redacted column may survive in anything that
|
|
246
|
+
* crosses (the entry's free text, the surviving column names, the surviving string cells).
|
|
247
|
+
* Numeric values are not searched for: digits recur in counts and amounts by coincidence.
|
|
248
|
+
*/
|
|
249
|
+
function redactionLeaks(original, dropped, redacted) {
|
|
250
|
+
const values = dropped.flatMap((column) => columnValues(original, column))
|
|
251
|
+
.filter((value) => typeof value === "string")
|
|
252
|
+
.map((value) => value.trim().toLowerCase())
|
|
253
|
+
.filter((value) => value.length >= LEAK_MIN_LENGTH);
|
|
254
|
+
if (values.length === 0)
|
|
255
|
+
return false;
|
|
256
|
+
const crossing = [...modelText(redacted), ...redacted.columns, ...cellStrings(redacted.rows)].map((text) => text.toLowerCase());
|
|
257
|
+
return values.some((value) => crossing.some((text) => text.includes(value)));
|
|
258
|
+
}
|
|
259
|
+
/** A refused slot crosses as its id, the status and the reason category, nothing else. */
|
|
260
|
+
function refusedAnswer(slot, reason) {
|
|
261
|
+
return { slot_id: slot.slot_id, status: "refused", reason_category: reason };
|
|
262
|
+
}
|
|
263
|
+
/** The disclosed shape of a table: columns and rows only. `definition`, SQL and every unknown key are dropped by construction. */
|
|
264
|
+
function discloseTable(slot, table, status) {
|
|
265
|
+
const value = slot.expected_shape === "scalar" ? columnValues(table, table.columns[0])[0] : undefined;
|
|
266
|
+
return { slot_id: slot.slot_id, status, shape: slot.expected_shape, columns: [...table.columns], rows: structuredClone(table.rows),
|
|
267
|
+
// Only the planner-declared unit crosses; the callee model's `unit` is model text the judge never sees.
|
|
268
|
+
...(value !== undefined ? { value } : {}), ...(slot.unit !== undefined ? { unit: slot.unit } : {}),
|
|
269
|
+
...(table.summary !== undefined ? { summary: table.summary } : {}) };
|
|
270
|
+
}
|
|
271
|
+
function provenanceOf(slot, table) {
|
|
272
|
+
const definition = table.definition && typeof table.definition === "object" ? table.definition : undefined;
|
|
273
|
+
return { slot_id: slot.slot_id, ...(table.definition !== undefined ? { definition: structuredClone(table.definition) } : {}),
|
|
274
|
+
...(typeof definition?.sql === "string" ? { sql: definition.sql } : {}),
|
|
275
|
+
...(Array.isArray(definition?.source_tables) ? { source_tables: definition.source_tables } : {}),
|
|
276
|
+
row_count: table.rows?.length ?? 0, columns: [...(table.columns ?? [])], ...(typeof table.verified === "boolean" ? { verified: table.verified } : {}) };
|
|
277
|
+
}
|
|
278
|
+
/** Reads the slot declarations from the alias-call input; absent slots mean one implicit table slot for the request text. */
|
|
279
|
+
export function readSlots(request) {
|
|
280
|
+
// The preamble is untrusted planner text either way; an object (the Hub `plan_report` shape) is serialised for the judge.
|
|
281
|
+
const rawPreamble = request.input["preamble"];
|
|
282
|
+
const preamble = typeof rawPreamble === "string" ? rawPreamble : rawPreamble !== undefined && rawPreamble !== null ? JSON.stringify(rawPreamble) : undefined;
|
|
283
|
+
// `input.slots` is this harness's declaration key; `input.questions` is the Hub `plan_report` call shape. Same entries.
|
|
284
|
+
const declared = request.input["slots"] !== undefined ? request.input["slots"] : request.input["questions"];
|
|
285
|
+
if (declared === undefined) {
|
|
286
|
+
return { slots: [{ slot_id: "answer", expected_shape: "table", question: request.request }], implicit: true, ...(preamble !== undefined ? { preamble } : {}) };
|
|
287
|
+
}
|
|
288
|
+
const parsed = slotsSchema.safeParse(declared);
|
|
289
|
+
if (!parsed.success)
|
|
290
|
+
return { error: "invalid_request" };
|
|
291
|
+
return { slots: parsed.data, implicit: false, ...(preamble !== undefined ? { preamble } : {}) };
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* What crosses for a per-slot child that failed or was refused as a whole: every declared slot
|
|
295
|
+
* as refused with the category that says what happened (`callee_error` for a child that failed
|
|
296
|
+
* with `callee_failed`, `callee_refused` for a refused child), so the caller can still lay out
|
|
297
|
+
* the report with unavailable cells. Only slot ids, the status and the category cross; nothing
|
|
298
|
+
* the child produced does. Undefined for a request without slots, which keeps the raw result.
|
|
299
|
+
*/
|
|
300
|
+
export function wholeChildDisclosure(request, reason) {
|
|
301
|
+
const read = readSlots(request);
|
|
302
|
+
if ("error" in read || read.implicit)
|
|
303
|
+
return undefined;
|
|
304
|
+
return { status: "ok", output: { kind: "value", value: { answers: read.slots.map((slot) => refusedAnswer(slot, reason)) } }, provenance: { verified: false } };
|
|
305
|
+
}
|
|
306
|
+
function classify(value) {
|
|
307
|
+
const table = tableSchema.safeParse(value);
|
|
308
|
+
if (table.success)
|
|
309
|
+
return { kind: "table", table: table.data };
|
|
310
|
+
const narrative = narrativeSchema.safeParse(value);
|
|
311
|
+
return narrative.success ? { kind: "narrative", narrative: narrative.data } : undefined;
|
|
312
|
+
}
|
|
313
|
+
function answersOf(value, slots) {
|
|
314
|
+
const map = new Map();
|
|
315
|
+
const single = classify(value);
|
|
316
|
+
if (single) {
|
|
317
|
+
if (slots.length !== 1)
|
|
318
|
+
return undefined;
|
|
319
|
+
map.set(slots[0].slot_id, single);
|
|
320
|
+
return map;
|
|
321
|
+
}
|
|
322
|
+
const batch = batchSchema.safeParse(value);
|
|
323
|
+
if (!batch.success)
|
|
324
|
+
return undefined;
|
|
325
|
+
const list = Array.isArray(batch.data) ? batch.data : batch.data.answers;
|
|
326
|
+
list.forEach((item, index) => {
|
|
327
|
+
const answer = classify(item);
|
|
328
|
+
const claimed = item && typeof item === "object" ? item.slot_id : undefined;
|
|
329
|
+
const id = typeof claimed === "string" ? claimed : slots[index]?.slot_id;
|
|
330
|
+
if (answer && id !== undefined && slots.some((slot) => slot.slot_id === id) && !map.has(id))
|
|
331
|
+
map.set(id, answer);
|
|
332
|
+
});
|
|
333
|
+
return map;
|
|
334
|
+
}
|
|
335
|
+
/** Slot ids the callee itself marked unanswerable or refused; only the category crosses, never the callee's reason text. */
|
|
336
|
+
function declinedOf(value) {
|
|
337
|
+
const list = Array.isArray(value) ? value : value && typeof value === "object" && Array.isArray(value.answers) ? value.answers : [];
|
|
338
|
+
const declined = new Set();
|
|
339
|
+
for (const item of list) {
|
|
340
|
+
const parsed = unavailableEntrySchema.safeParse(item);
|
|
341
|
+
if (parsed.success)
|
|
342
|
+
declined.add(parsed.data.slot_id);
|
|
343
|
+
}
|
|
344
|
+
return declined;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Verifies one callee result against the disclosure policy, slot by slot,
|
|
348
|
+
* and returns what the caller may see plus the host-side decisions and
|
|
349
|
+
* provenance. Deterministic checks run first; only a slot that passes them
|
|
350
|
+
* reaches the judge. On `pass` and `redact` the disclosed answer is rebuilt
|
|
351
|
+
* from columns and rows alone, so `definition`, SQL and any other key the
|
|
352
|
+
* callee attached never cross. Text the callee's model wrote (`summary`,
|
|
353
|
+
* `text`, `unit`) is scanned before the judge, crosses only on `pass`, and on
|
|
354
|
+
* `redact` is replaced by a host-written sentence naming the row count and
|
|
355
|
+
* the surviving columns.
|
|
356
|
+
*/
|
|
357
|
+
export async function verifyEgress(request, result, options) {
|
|
358
|
+
const read = readSlots(request);
|
|
359
|
+
if ("error" in read) {
|
|
360
|
+
return { disclosed: { status: "refused", code: "callee_refused", message: `egress refused: ${read.error}` },
|
|
361
|
+
decisions: [{ slot_id: "*", status: "refused", reason_category: read.error, judge: "skipped", row_count: 0 }], provenance: [] };
|
|
362
|
+
}
|
|
363
|
+
const { slots, implicit, preamble } = read;
|
|
364
|
+
if (result.status === "refused") {
|
|
365
|
+
const decisions = slots.map((slot) => ({ slot_id: slot.slot_id, status: "refused", reason_category: "callee_refused", judge: "skipped", row_count: 0 }));
|
|
366
|
+
return { disclosed: wholeChildDisclosure(request, "callee_refused") ?? result, decisions, provenance: [] };
|
|
367
|
+
}
|
|
368
|
+
if (result.status === "error") {
|
|
369
|
+
const decisions = slots.map((slot) => ({ slot_id: slot.slot_id, status: "refused", reason_category: "callee_error", judge: "skipped", row_count: 0 }));
|
|
370
|
+
const unavailable = result.code === "callee_failed" ? wholeChildDisclosure(request, "callee_error") : undefined;
|
|
371
|
+
return { disclosed: unavailable ?? result, decisions, provenance: [] };
|
|
372
|
+
}
|
|
373
|
+
const answers = result.output.kind === "value" ? answersOf(result.output.value, slots) : undefined;
|
|
374
|
+
const declinedByCallee = result.output.kind === "value" ? declinedOf(result.output.value) : new Set();
|
|
375
|
+
const decisions = [];
|
|
376
|
+
const provenance = [];
|
|
377
|
+
const disclosed = [];
|
|
378
|
+
for (const slot of slots) {
|
|
379
|
+
const answer = answers?.get(slot.slot_id);
|
|
380
|
+
if (!answer) {
|
|
381
|
+
const reason = declinedByCallee.has(slot.slot_id) ? "unanswerable" : "invalid_answer";
|
|
382
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: reason, judge: "skipped", row_count: 0 });
|
|
383
|
+
disclosed.push(refusedAnswer(slot, reason));
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (answer.kind === "narrative") {
|
|
387
|
+
const text = answer.narrative.text;
|
|
388
|
+
const reason = deterministicNarrative(slot, text, options.policy, answer.narrative.unit);
|
|
389
|
+
provenance.push(provenanceOf(slot, answer.narrative));
|
|
390
|
+
if (reason) {
|
|
391
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: reason, judge: "skipped", row_count: 0 });
|
|
392
|
+
disclosed.push(refusedAnswer(slot, reason));
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const judged = await consultJudge(options.judge, { policy: options.policy, untrusted_question: slot.question, ...(preamble !== undefined ? { untrusted_preamble: preamble } : {}),
|
|
396
|
+
expected_shape: slot.expected_shape, answer: { columns: [], rows: [], text }, metadata: { row_count: 0, group_count: 0, identifier_like_keys: [], columns_touched: [] } }, options.policy, options.signal);
|
|
397
|
+
if (judged.verdict !== "pass") {
|
|
398
|
+
// A narrative answer has no columns to drop: a redact verdict on it cannot be carried through.
|
|
399
|
+
const reason = "reason" in judged ? judged.reason : "redact_leak";
|
|
400
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: reason, judge: judged.verdict, row_count: 0 });
|
|
401
|
+
disclosed.push(refusedAnswer(slot, reason));
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
decisions.push({ slot_id: slot.slot_id, status: "ok", judge: "pass", row_count: 0 });
|
|
405
|
+
disclosed.push({ slot_id: slot.slot_id, status: "ok", shape: "narrative", text, ...(slot.unit !== undefined ? { unit: slot.unit } : {}) });
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
provenance.push(provenanceOf(slot, answer.table));
|
|
409
|
+
let table = answer.table;
|
|
410
|
+
const reason = deterministic(slot, table, options.policy);
|
|
411
|
+
if (reason) {
|
|
412
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: reason, judge: "skipped", row_count: table.rows.length });
|
|
413
|
+
disclosed.push(refusedAnswer(slot, reason));
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const judged = await consultJudge(options.judge, { policy: options.policy, untrusted_question: slot.question, ...(preamble !== undefined ? { untrusted_preamble: preamble } : {}),
|
|
417
|
+
expected_shape: slot.expected_shape, answer: { columns: table.columns, rows: table.rows, ...(table.summary !== undefined ? { summary: table.summary } : {}) },
|
|
418
|
+
metadata: { row_count: table.rows.length, group_count: groupCount(table), identifier_like_keys: identifierLikeKeys(table), columns_touched: [...table.columns] } }, options.policy, options.signal);
|
|
419
|
+
if (judged.verdict === "refuse") {
|
|
420
|
+
const reasonCategory = "reason" in judged ? judged.reason : "judge_refused";
|
|
421
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: reasonCategory, judge: "refuse", row_count: table.rows.length });
|
|
422
|
+
disclosed.push(refusedAnswer(slot, reasonCategory));
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
let status = "ok";
|
|
426
|
+
if (judged.verdict === "redact") {
|
|
427
|
+
const dropped = judged.redact_columns ?? [];
|
|
428
|
+
const redacted = redactColumns(table, dropped);
|
|
429
|
+
const again = !redacted ? "judge_invalid" : deterministic(slot, redacted, options.policy)
|
|
430
|
+
?? (redactionLeaks(table, dropped, redacted) ? "redact_leak" : undefined)
|
|
431
|
+
// A narrative slot's answer is the model's prose itself; after a redact verdict no model text crosses.
|
|
432
|
+
?? (slot.expected_shape === "narrative" ? "redact_leak" : undefined);
|
|
433
|
+
if (!redacted || again) {
|
|
434
|
+
const reasonCategory = again ?? "judge_invalid";
|
|
435
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: reasonCategory, judge: "redact", row_count: table.rows.length });
|
|
436
|
+
disclosed.push(refusedAnswer(slot, reasonCategory));
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
table = redacted;
|
|
440
|
+
status = "partial";
|
|
441
|
+
}
|
|
442
|
+
if (slot.expected_shape === "narrative") {
|
|
443
|
+
// Narrowing: only the prose crosses for a narrative slot, never the rows it was grounded in.
|
|
444
|
+
const text = table.summary.trim();
|
|
445
|
+
const textReason = deterministicNarrative(slot, text, options.policy);
|
|
446
|
+
if (textReason) {
|
|
447
|
+
decisions.push({ slot_id: slot.slot_id, status: "refused", reason_category: textReason, judge: judged.verdict, row_count: table.rows.length });
|
|
448
|
+
disclosed.push(refusedAnswer(slot, textReason));
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
decisions.push({ slot_id: slot.slot_id, status, judge: judged.verdict, row_count: table.rows.length });
|
|
452
|
+
disclosed.push({ slot_id: slot.slot_id, status, shape: "narrative", text, ...(slot.unit !== undefined ? { unit: slot.unit } : {}) });
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
decisions.push({ slot_id: slot.slot_id, status, judge: judged.verdict, row_count: table.rows.length });
|
|
456
|
+
disclosed.push(discloseTable(slot, table, status));
|
|
457
|
+
}
|
|
458
|
+
const anyDisclosed = disclosed.some((answer) => answer.status !== "refused");
|
|
459
|
+
if (implicit) {
|
|
460
|
+
const only = disclosed[0];
|
|
461
|
+
if (only.status === "refused") {
|
|
462
|
+
return { disclosed: { status: "refused", code: "callee_refused", message: `egress refused: ${only.reason_category}` }, decisions, provenance };
|
|
463
|
+
}
|
|
464
|
+
// A single implicit slot keeps the tabular value contract callers already consume, minus definition/SQL.
|
|
465
|
+
return { disclosed: { status: "ok", output: { kind: "value", value: { columns: only.columns, rows: only.rows, verified: true,
|
|
466
|
+
...(only.summary !== undefined ? { summary: only.summary } : {}), egress: { status: only.status } } }, provenance: { verified: true } }, decisions, provenance };
|
|
467
|
+
}
|
|
468
|
+
return { disclosed: { status: "ok", output: { kind: "value", value: { answers: disclosed } }, provenance: { verified: anyDisclosed } }, decisions, provenance };
|
|
469
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InProcessOptions } from "../route/types.js";
|
|
2
|
+
import type { RunAgentResult } from "../session/types.js";
|
|
3
|
+
import { type ExecutionPlan } from "./runner.js";
|
|
4
|
+
/** Executable format 0.2 path; never flattened into the legacy agent tool union. */
|
|
5
|
+
export declare function runInProcessComponents(plan: ExecutionPlan, options: InProcessOptions): Promise<RunAgentResult>;
|
|
6
|
+
/** `answer_query` when the plan has it, else the plan's single entry; a multi-entry plan without `answer_query` keeps the historical default and fails as before. */
|
|
7
|
+
export declare function defaultEntry(plan: ExecutionPlan): string;
|