@honua/mcp-server 0.1.10-beta.0 → 0.1.11-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/certification/setup-parity.md +79 -0
- package/dist/src/certification/setup-parity.d.ts +86 -0
- package/dist/src/certification/setup-parity.d.ts.map +1 -0
- package/dist/src/certification/setup-parity.js +188 -0
- package/dist/src/eval/cli.js +2 -2
- package/dist/src/eval/corpus.d.ts.map +1 -1
- package/dist/src/eval/corpus.js +6 -5
- package/dist/src/eval/grade.d.ts.map +1 -1
- package/dist/src/eval/grade.js +25 -0
- package/dist/src/eval/report.d.ts +9 -0
- package/dist/src/eval/report.d.ts.map +1 -1
- package/dist/src/eval/report.js +15 -5
- package/dist/src/eval/runner.d.ts.map +1 -1
- package/dist/src/eval/runner.js +16 -1
- package/dist/src/eval/types.d.ts +37 -2
- package/dist/src/eval/types.d.ts.map +1 -1
- package/dist/src/eval/workflow-corpus.d.ts +9 -0
- package/dist/src/eval/workflow-corpus.d.ts.map +1 -0
- package/dist/src/eval/workflow-corpus.js +306 -0
- package/dist/src/proxy.d.ts.map +1 -1
- package/dist/src/proxy.js +7 -1
- package/dist/src/release/zero-to-map-cli.d.ts.map +1 -1
- package/dist/src/release/zero-to-map-cli.js +7 -10
- package/dist/src/release/zero-to-map.d.ts +36 -69
- package/dist/src/release/zero-to-map.d.ts.map +1 -1
- package/dist/src/release/zero-to-map.js +102 -178
- package/package.json +4 -2
- package/release/zero-to-map/README.md +61 -83
- package/release/zero-to-map/configs/claude-code.mcp.json +1 -1
- package/release/zero-to-map/configs/claude-desktop.json +1 -1
- package/release/zero-to-map/configs/cursor.json +1 -1
- package/release/zero-to-map/journey.v1.json +163 -751
- package/release/zero-to-map/contracts/esri-gp-mcp.v1.json +0 -73
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Bounded setup catalog qualification
|
|
2
|
+
|
|
3
|
+
The 2026.1 setup gate uses the candidate's server-authored workflow view. It
|
|
4
|
+
does not require the legacy full Admin, analysis and Esri GP roster. Run the
|
|
5
|
+
catalog check from an installed, integrity-verified MCP package:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node node_modules/@honua/mcp-server/dist/src/certification/setup-parity.js \
|
|
9
|
+
reviewed-server-manifest.json <independently-pinned-manifest-sha256> \
|
|
10
|
+
setup-catalog-receipt.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
From source, the equivalent command is `npm run certify:setup-parity -- ...`
|
|
14
|
+
in `mcp/`. Configure `HONUA_MCP_REMOTE_URL` and exactly one credential source:
|
|
15
|
+
`HONUA_MCP_AUTH_TOKEN`, `HONUA_ADMIN_KEY`, or `HONUA_API_KEY`. Credentials must
|
|
16
|
+
not appear in arguments, URLs, the manifest or receipts. The existing proxy
|
|
17
|
+
enforces HTTPS for credential-bearing endpoints, with the local loopback
|
|
18
|
+
exception used by the Docker installer.
|
|
19
|
+
|
|
20
|
+
The manifest is a reviewed capture from the pinned server under the intended
|
|
21
|
+
effective permissions. Keep its SHA-256 in the release lock/evidence index,
|
|
22
|
+
separate from the captured file. It has this structure:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"schemaVersion": "honua.setup-catalog-manifest/v1",
|
|
27
|
+
"candidateId": "manifest-sha256:<release-manifest-digest>",
|
|
28
|
+
"serverImage": "ghcr.io/honua-io/honua-server@sha256:<image-digest>",
|
|
29
|
+
"packages": [
|
|
30
|
+
{ "name": "@honua/sdk-js", "version": "<exact-version>", "integrity": "sha512-<registry-integrity>" },
|
|
31
|
+
{ "name": "@honua/mcp-server", "version": "<exact-version>", "integrity": "sha512-<registry-integrity>" }
|
|
32
|
+
],
|
|
33
|
+
"serverInfo": { "name": "<initialize serverInfo.name>", "version": "<initialize serverInfo.version>" },
|
|
34
|
+
"view": "<server-published bounded view>",
|
|
35
|
+
"revision": "<tools/list _meta.revision>",
|
|
36
|
+
"revisionDigest": "<tools/list _meta.revisionDigest>",
|
|
37
|
+
"membershipDigest": "<tools/list _meta.membershipDigest>",
|
|
38
|
+
"descriptorDigest": "<tools/list _meta.descriptorDigest>",
|
|
39
|
+
"tools": ["<complete original tool objects from all pages, not names or abbreviated schemas>"]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The placeholders above illustrate the shape and are deliberately invalid as
|
|
44
|
+
qualification input. Discover the view from the server's capabilities; do not
|
|
45
|
+
invent profile switches or assemble a replacement tool roster in the client.
|
|
46
|
+
Retain complete descriptions, input/output schemas, annotations and `_meta`.
|
|
47
|
+
The server's opaque digests are compared to the reviewed pin. The receipt also
|
|
48
|
+
computes independent normalized descriptor hashes for HTTP and stdio; these
|
|
49
|
+
use a different serialization from the server and have distinct field names.
|
|
50
|
+
|
|
51
|
+
The check drains all pages over direct HTTP and a real child-process stdio
|
|
52
|
+
proxy, carrying the selected view on every page. It fails on missing, extra,
|
|
53
|
+
duplicate or changed tools, missing/changed revision metadata, server identity
|
|
54
|
+
drift, cursor loops and proxy differences. Missing tools identify candidate
|
|
55
|
+
view/effective permissions as the diagnostic boundary. No total such as 441 is
|
|
56
|
+
used to decide success, and a complete but short catalog is a membership
|
|
57
|
+
failure rather than a guessed pagination fault.
|
|
58
|
+
|
|
59
|
+
The receipt's scope is `catalog-parity-only`. Its candidate image and package
|
|
60
|
+
integrities are **bindings from the reviewed manifest**, not a claim that this
|
|
61
|
+
command inspected the running image or verified installed package bytes.
|
|
62
|
+
The release producer must verify those bindings against its immutable install
|
|
63
|
+
receipt before consuming this result. This read-only check does not install a
|
|
64
|
+
server, mutate styles, prove `geometry.buffer` execution, validate effective
|
|
65
|
+
permissions, or satisfy all of #1401. Retain the separate clean-install,
|
|
66
|
+
readiness/auth, bounded task execution, get-style → apply-style-preset →
|
|
67
|
+
render-map → read-resource PNG, and versioned-guidance receipts for
|
|
68
|
+
honua-release#123/#129/#161. Existing `assertRenderedPng` checks reject empty
|
|
69
|
+
or flat renders; a catalog match alone cannot establish style success.
|
|
70
|
+
|
|
71
|
+
`honua admin qualify` joins this receipt to the local install pin, effective
|
|
72
|
+
grants, server identity, and the style/render PNG proof. It re-reads the
|
|
73
|
+
bounded view over HTTP, requires this receipt's HTTP and stdio descriptor
|
|
74
|
+
hashes to match, and still does not treat a catalog match as style success.
|
|
75
|
+
|
|
76
|
+
The older zero-to-map runner retains its broader workflow as an explicit
|
|
77
|
+
legacy integration exercise. It must not be used as the 2026.1 bounded catalog
|
|
78
|
+
gate, or its full Admin/analysis prerequisites would reintroduce deferred
|
|
79
|
+
scope into first-cut qualification.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
/** A reviewed capture from the pinned server, never a client-authored tool roster. */
|
|
4
|
+
export interface SetupCatalogManifest {
|
|
5
|
+
schemaVersion: "honua.setup-catalog-manifest/v1";
|
|
6
|
+
candidateId: string;
|
|
7
|
+
serverImage: string;
|
|
8
|
+
packages: {
|
|
9
|
+
name: string;
|
|
10
|
+
version: string;
|
|
11
|
+
integrity: string;
|
|
12
|
+
}[];
|
|
13
|
+
serverInfo: {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
};
|
|
17
|
+
view: string;
|
|
18
|
+
revision: string;
|
|
19
|
+
revisionDigest: string;
|
|
20
|
+
membershipDigest: string;
|
|
21
|
+
descriptorDigest: string;
|
|
22
|
+
tools: Tool[];
|
|
23
|
+
}
|
|
24
|
+
export interface SetupCatalogSnapshot {
|
|
25
|
+
tools: Tool[];
|
|
26
|
+
metadata: Record<string, unknown>;
|
|
27
|
+
serverInfo: unknown;
|
|
28
|
+
}
|
|
29
|
+
export declare function parseSetupCatalogManifest(bytes: string, expectedSha256: string): SetupCatalogManifest;
|
|
30
|
+
/** Drain the selected view, retaining server revision metadata across every page. */
|
|
31
|
+
export declare function readSetupCatalog(client: {
|
|
32
|
+
listTools(request?: {
|
|
33
|
+
cursor?: string;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
tools: Tool[];
|
|
36
|
+
nextCursor?: string;
|
|
37
|
+
_meta?: Record<string, unknown>;
|
|
38
|
+
}>;
|
|
39
|
+
getServerVersion(): unknown;
|
|
40
|
+
}, view: string): Promise<SetupCatalogSnapshot>;
|
|
41
|
+
/** Catalog evidence only: installation, authorization and style execution have separate receipts. */
|
|
42
|
+
export declare function certifySetupCatalogParity(manifest: SetupCatalogManifest, manifestSha256: string, direct: SetupCatalogSnapshot, proxied: SetupCatalogSnapshot): {
|
|
43
|
+
schemaVersion: "honua.setup-catalog-parity/v1";
|
|
44
|
+
scope: "catalog-parity-only";
|
|
45
|
+
candidateId: string;
|
|
46
|
+
serverImage: string;
|
|
47
|
+
packages: {
|
|
48
|
+
name: string;
|
|
49
|
+
version: string;
|
|
50
|
+
integrity: string;
|
|
51
|
+
}[];
|
|
52
|
+
manifestSha256: string;
|
|
53
|
+
view: string;
|
|
54
|
+
revision: string;
|
|
55
|
+
revisionDigest: string;
|
|
56
|
+
expectedTools: number;
|
|
57
|
+
directTools: number;
|
|
58
|
+
proxiedTools: number;
|
|
59
|
+
directDescriptorSha256: string;
|
|
60
|
+
proxiedDescriptorSha256: string;
|
|
61
|
+
pass: boolean;
|
|
62
|
+
differences: string[];
|
|
63
|
+
};
|
|
64
|
+
export declare function runSetupParityCertification(manifestPath: string, manifestSha256: string, outputPath: string, env?: NodeJS.ProcessEnv): Promise<{
|
|
65
|
+
schemaVersion: "honua.setup-catalog-parity/v1";
|
|
66
|
+
scope: "catalog-parity-only";
|
|
67
|
+
candidateId: string;
|
|
68
|
+
serverImage: string;
|
|
69
|
+
packages: {
|
|
70
|
+
name: string;
|
|
71
|
+
version: string;
|
|
72
|
+
integrity: string;
|
|
73
|
+
}[];
|
|
74
|
+
manifestSha256: string;
|
|
75
|
+
view: string;
|
|
76
|
+
revision: string;
|
|
77
|
+
revisionDigest: string;
|
|
78
|
+
expectedTools: number;
|
|
79
|
+
directTools: number;
|
|
80
|
+
proxiedTools: number;
|
|
81
|
+
directDescriptorSha256: string;
|
|
82
|
+
proxiedDescriptorSha256: string;
|
|
83
|
+
pass: boolean;
|
|
84
|
+
differences: string[];
|
|
85
|
+
}>;
|
|
86
|
+
//# sourceMappingURL=setup-parity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-parity.d.ts","sourceRoot":"","sources":["../../../src/certification/setup-parity.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,KAAK,IAAI,EAAc,MAAM,oCAAoC,CAAC;AAK3E,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,iCAAiC,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;CACrB;AAsBD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,oBAAoB,CA+CrG;AAED,qFAAqF;AACrF,wBAAsB,gBAAgB,CACpC,MAAM,EAAE;IACN,SAAS,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAChD,KAAK,EAAE,IAAI,EAAE,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC,CAAC;IACH,gBAAgB,IAAI,OAAO,CAAC;CAC7B,EACD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,oBAAoB,CAAC,CAiB/B;AAED,qGAAqG;AACrG,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,oBAAoB,EAC9B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,oBAAoB;;;;;;;;;;;;;;;;;;;;;EAgD9B;AAED,wBAAsB,2BAA2B,CAC/C,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB;;;;;;;;;;;;;;;;;;;;;GA0BrC"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { isMainEntrypoint } from "../entrypoint.js";
|
|
9
|
+
import { connectUpstream, resolveProxyOptions } from "../proxy.js";
|
|
10
|
+
import { listAllTools } from "./admin-parity.js";
|
|
11
|
+
function stableJson(value) {
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
return `{${Object.entries(value)
|
|
16
|
+
.filter(([, item]) => item !== undefined)
|
|
17
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
18
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
|
|
19
|
+
.join(",")}}`;
|
|
20
|
+
}
|
|
21
|
+
return JSON.stringify(value) ?? "null";
|
|
22
|
+
}
|
|
23
|
+
function sha256(value) {
|
|
24
|
+
return createHash("sha256").update(value).digest("hex");
|
|
25
|
+
}
|
|
26
|
+
function record(value) {
|
|
27
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
export function parseSetupCatalogManifest(bytes, expectedSha256) {
|
|
30
|
+
if (!/^[a-f0-9]{64}$/.test(expectedSha256) || sha256(bytes) !== expectedSha256) {
|
|
31
|
+
throw new Error("Setup manifest SHA-256 does not match the independently pinned digest");
|
|
32
|
+
}
|
|
33
|
+
const value = JSON.parse(bytes);
|
|
34
|
+
if (!record(value) || value.schemaVersion !== "honua.setup-catalog-manifest/v1") {
|
|
35
|
+
throw new Error("Expected honua.setup-catalog-manifest/v1");
|
|
36
|
+
}
|
|
37
|
+
for (const key of ["candidateId", "view", "revision"]) {
|
|
38
|
+
if (typeof value[key] !== "string" || !value[key].trim())
|
|
39
|
+
throw new Error(`Manifest requires ${key}`);
|
|
40
|
+
}
|
|
41
|
+
if (value.view === "full")
|
|
42
|
+
throw new Error("Setup qualification requires a bounded server-authored view");
|
|
43
|
+
if (typeof value.serverImage !== "string" || !/^[^\s@]+@sha256:[a-f0-9]{64}$/.test(value.serverImage)) {
|
|
44
|
+
throw new Error("Manifest requires an immutable server image digest");
|
|
45
|
+
}
|
|
46
|
+
for (const key of ["revisionDigest", "membershipDigest", "descriptorDigest"]) {
|
|
47
|
+
if (typeof value[key] !== "string" || !/^[a-f0-9]{64}$/.test(value[key])) {
|
|
48
|
+
throw new Error(`Manifest requires server-authored ${key}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!record(value.serverInfo) ||
|
|
52
|
+
typeof value.serverInfo.name !== "string" ||
|
|
53
|
+
typeof value.serverInfo.version !== "string") {
|
|
54
|
+
throw new Error("Manifest requires the server initialize identity");
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(value.packages) ||
|
|
57
|
+
value.packages.length === 0 ||
|
|
58
|
+
value.packages.some((pkg) => !record(pkg) ||
|
|
59
|
+
typeof pkg.name !== "string" ||
|
|
60
|
+
!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(pkg.name) ||
|
|
61
|
+
typeof pkg.version !== "string" ||
|
|
62
|
+
!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(pkg.version) ||
|
|
63
|
+
typeof pkg.integrity !== "string" ||
|
|
64
|
+
!/^sha512-[A-Za-z0-9+/]{86}==$/.test(pkg.integrity)))
|
|
65
|
+
throw new Error("Manifest requires exact package versions and sha512 integrities");
|
|
66
|
+
if (!Array.isArray(value.tools) || value.tools.length === 0)
|
|
67
|
+
throw new Error("Manifest has no enabled tools");
|
|
68
|
+
for (const tool of value.tools)
|
|
69
|
+
ToolSchema.parse(tool);
|
|
70
|
+
const names = value.tools.map((tool) => tool.name);
|
|
71
|
+
if (new Set(names).size !== names.length)
|
|
72
|
+
throw new Error("Manifest repeats a tool name");
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
/** Drain the selected view, retaining server revision metadata across every page. */
|
|
76
|
+
export async function readSetupCatalog(client, view) {
|
|
77
|
+
let metadata;
|
|
78
|
+
const tools = await listAllTools({
|
|
79
|
+
async listTools(request) {
|
|
80
|
+
const page = await client.listTools(request);
|
|
81
|
+
if (!page._meta)
|
|
82
|
+
throw new Error("tools/list omitted the server-authored view metadata");
|
|
83
|
+
if (metadata && stableJson(metadata) !== stableJson(page._meta)) {
|
|
84
|
+
throw new Error("tools/list changed view metadata during pagination; repeat against a stable candidate");
|
|
85
|
+
}
|
|
86
|
+
metadata = page._meta;
|
|
87
|
+
return page;
|
|
88
|
+
},
|
|
89
|
+
}, { view });
|
|
90
|
+
return { tools, metadata: metadata ?? {}, serverInfo: client.getServerVersion() };
|
|
91
|
+
}
|
|
92
|
+
/** Catalog evidence only: installation, authorization and style execution have separate receipts. */
|
|
93
|
+
export function certifySetupCatalogParity(manifest, manifestSha256, direct, proxied) {
|
|
94
|
+
const differences = [];
|
|
95
|
+
const expected = new Map(manifest.tools.map((tool) => [tool.name, tool]));
|
|
96
|
+
for (const [transport, snapshot] of [
|
|
97
|
+
["HTTP", direct],
|
|
98
|
+
["stdio", proxied],
|
|
99
|
+
]) {
|
|
100
|
+
if (stableJson(snapshot.serverInfo) !== stableJson(manifest.serverInfo))
|
|
101
|
+
differences.push(`${transport}: server identity differs from the pin`);
|
|
102
|
+
const names = snapshot.tools.map((tool) => tool.name);
|
|
103
|
+
if (new Set(names).size !== names.length)
|
|
104
|
+
differences.push(`${transport}: duplicate tool names`);
|
|
105
|
+
for (const key of ["view", "revision", "revisionDigest", "membershipDigest", "descriptorDigest"]) {
|
|
106
|
+
if (snapshot.metadata[key] !== manifest[key])
|
|
107
|
+
differences.push(`${transport}: ${key} differs from the pin`);
|
|
108
|
+
}
|
|
109
|
+
if (snapshot.metadata.toolCount !== manifest.tools.length)
|
|
110
|
+
differences.push(`${transport}: declared toolCount differs from the enabled manifest`);
|
|
111
|
+
const actual = new Map(snapshot.tools.map((tool) => [tool.name, tool]));
|
|
112
|
+
for (const name of expected.keys()) {
|
|
113
|
+
if (!actual.has(name))
|
|
114
|
+
differences.push(`${transport}: missing enabled tool ${name}; check candidate view and effective permissions`);
|
|
115
|
+
else if (stableJson(actual.get(name)) !== stableJson(expected.get(name)))
|
|
116
|
+
differences.push(`${transport}: ${name} descriptor differs from the pin`);
|
|
117
|
+
}
|
|
118
|
+
for (const name of actual.keys())
|
|
119
|
+
if (!expected.has(name))
|
|
120
|
+
differences.push(`${transport}: unexpected tool ${name}`);
|
|
121
|
+
}
|
|
122
|
+
if (stableJson(direct.metadata) !== stableJson(proxied.metadata))
|
|
123
|
+
differences.push("HTTP/stdio view metadata differs");
|
|
124
|
+
const descriptorHash = (tools) => sha256(stableJson([...tools].sort((a, b) => a.name.localeCompare(b.name))));
|
|
125
|
+
return {
|
|
126
|
+
schemaVersion: "honua.setup-catalog-parity/v1",
|
|
127
|
+
scope: "catalog-parity-only",
|
|
128
|
+
candidateId: manifest.candidateId,
|
|
129
|
+
serverImage: manifest.serverImage,
|
|
130
|
+
packages: manifest.packages.map(({ name, version, integrity }) => ({ name, version, integrity })),
|
|
131
|
+
manifestSha256,
|
|
132
|
+
view: manifest.view,
|
|
133
|
+
revision: manifest.revision,
|
|
134
|
+
revisionDigest: manifest.revisionDigest,
|
|
135
|
+
expectedTools: manifest.tools.length,
|
|
136
|
+
directTools: direct.tools.length,
|
|
137
|
+
proxiedTools: proxied.tools.length,
|
|
138
|
+
directDescriptorSha256: descriptorHash(direct.tools),
|
|
139
|
+
proxiedDescriptorSha256: descriptorHash(proxied.tools),
|
|
140
|
+
pass: differences.length === 0,
|
|
141
|
+
differences,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export async function runSetupParityCertification(manifestPath, manifestSha256, outputPath, env = process.env) {
|
|
145
|
+
const manifest = parseSetupCatalogManifest(await readFile(manifestPath, "utf8"), manifestSha256);
|
|
146
|
+
const options = resolveProxyOptions(env);
|
|
147
|
+
const upstream = await connectUpstream(options);
|
|
148
|
+
const proxied = new Client({ name: "honua-setup-parity", version: "1.0.0" });
|
|
149
|
+
const transport = new StdioClientTransport({
|
|
150
|
+
command: process.execPath,
|
|
151
|
+
args: [fileURLToPath(new URL("../proxy.js", import.meta.url))],
|
|
152
|
+
env: Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined)),
|
|
153
|
+
stderr: "pipe",
|
|
154
|
+
});
|
|
155
|
+
// The proxy may report upstream error bodies. Never copy those into a receipt or terminal log.
|
|
156
|
+
transport.stderr?.on("data", () => { });
|
|
157
|
+
try {
|
|
158
|
+
await proxied.connect(transport);
|
|
159
|
+
const direct = await readSetupCatalog(upstream, manifest.view);
|
|
160
|
+
const forwarded = await readSetupCatalog(proxied, manifest.view);
|
|
161
|
+
const receipt = certifySetupCatalogParity(manifest, manifestSha256, direct, forwarded);
|
|
162
|
+
await writeFile(outputPath, `${JSON.stringify(receipt, null, 2)}\n`, "utf8");
|
|
163
|
+
return receipt;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
await proxied.close().catch(() => { });
|
|
167
|
+
await transport.close().catch(() => { });
|
|
168
|
+
await upstream.close().catch(() => { });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (isMainEntrypoint(import.meta.url)) {
|
|
172
|
+
const [manifestPath, digest, outputPath] = process.argv.slice(2);
|
|
173
|
+
if (!manifestPath || !digest || !outputPath || process.argv.length !== 5) {
|
|
174
|
+
process.stderr.write("Usage: setup-parity <server-manifest.json> <pinned-sha256> <receipt.json>\n");
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
runSetupParityCertification(manifestPath, digest, outputPath)
|
|
179
|
+
.then((receipt) => {
|
|
180
|
+
process.stdout.write(`${receipt.pass ? "PASS" : "FAIL"}: bounded setup catalog parity (${receipt.expectedTools} enabled tools)\n`);
|
|
181
|
+
process.exitCode = receipt.pass ? 0 : 1;
|
|
182
|
+
})
|
|
183
|
+
.catch(() => {
|
|
184
|
+
process.stderr.write("Setup parity could not complete; verify the pinned manifest, endpoint, credentials and server view. Upstream details suppressed.\n");
|
|
185
|
+
process.exitCode = 1;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
package/dist/src/eval/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ import { runEval } from "./runner.js";
|
|
|
22
22
|
* explicit request yields an honest driverError, not a silent
|
|
23
23
|
* skip. Without --driver, drivers are auto-resolved from env.
|
|
24
24
|
* --corpus <name> Select the corpus explicitly: analyst | operator | northstar |
|
|
25
|
-
* standalone | ogc | all. This overrides HONUA_EVAL_CORPUS and is how the `eval:live`
|
|
25
|
+
* standalone | ogc | workflow | all. This overrides HONUA_EVAL_CORPUS and is how the `eval:live`
|
|
26
26
|
* script targets the operator surface without relying on shell env
|
|
27
27
|
* (cross-platform). `northstar` targets the three P1-gate
|
|
28
28
|
* workflows; `standalone` and `ogc` target the two platform-free
|
|
@@ -39,7 +39,7 @@ import { runEval } from "./runner.js";
|
|
|
39
39
|
* chain), and live remote MCP runs when HONUA_MCP_REMOTE_URL is set. Keys are
|
|
40
40
|
* never hardcoded — they come from the environment.
|
|
41
41
|
*/
|
|
42
|
-
const CORPUS_NAMES = ["analyst", "operator", "northstar", "standalone", "ogc", "all"];
|
|
42
|
+
const CORPUS_NAMES = ["analyst", "operator", "northstar", "standalone", "ogc", "workflow", "all"];
|
|
43
43
|
function parseArgs(argv) {
|
|
44
44
|
let artifactOnly = false;
|
|
45
45
|
let offline = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/corpus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/corpus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,EAAE,QAAQ,EAkG5B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,QAAQ,EAAE,CAmB9E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEhF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEzE"}
|
package/dist/src/eval/corpus.js
CHANGED
|
@@ -2,6 +2,7 @@ import { NORTHSTAR_CORPUS } from "./northstar-corpus.js";
|
|
|
2
2
|
import { OGC_CORPUS } from "./ogc-corpus.js";
|
|
3
3
|
import { OPERATOR_CORPUS } from "./operator-corpus.js";
|
|
4
4
|
import { STANDALONE_CORPUS } from "./standalone-corpus.js";
|
|
5
|
+
import { WORKFLOW_CORPUS } from "./workflow-corpus.js";
|
|
5
6
|
/**
|
|
6
7
|
* Held-out corpus of GIS workflows for the cross-model eval (honua-server #1956).
|
|
7
8
|
*
|
|
@@ -132,12 +133,12 @@ export function resolveCorpus(env = process.env) {
|
|
|
132
133
|
return STANDALONE_CORPUS;
|
|
133
134
|
case "ogc":
|
|
134
135
|
return OGC_CORPUS;
|
|
136
|
+
case "workflow":
|
|
137
|
+
return WORKFLOW_CORPUS;
|
|
135
138
|
case "all":
|
|
136
|
-
// `all` spans the Honua-surface corpora
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
// census-specific scenarios against an operator surface.
|
|
140
|
-
return [...CORPUS, ...OPERATOR_CORPUS, ...NORTHSTAR_CORPUS];
|
|
139
|
+
// `all` spans the Honua-surface corpora. Standalone and OGC stay out:
|
|
140
|
+
// they target a different fixture surface. Workflow is the same surface.
|
|
141
|
+
return [...CORPUS, ...OPERATOR_CORPUS, ...NORTHSTAR_CORPUS, ...WORKFLOW_CORPUS];
|
|
141
142
|
default:
|
|
142
143
|
return CORPUS;
|
|
143
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grade.d.ts","sourceRoot":"","sources":["../../../src/eval/grade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9E,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAQjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"grade.d.ts","sourceRoot":"","sources":["../../../src/eval/grade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9E,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAQjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,GAAG,aAAa,CA4FvF"}
|
package/dist/src/eval/grade.js
CHANGED
|
@@ -24,6 +24,9 @@ export function grade(scenario, transcript) {
|
|
|
24
24
|
errorCount: transcript.errorCount,
|
|
25
25
|
};
|
|
26
26
|
const { criteria } = scenario;
|
|
27
|
+
if (transcript.blockedReason) {
|
|
28
|
+
return { ...base, outcome: "blocked", violations: [transcript.blockedReason] };
|
|
29
|
+
}
|
|
27
30
|
if (transcript.driverError) {
|
|
28
31
|
return { ...base, outcome: "error", violations: [transcript.driverError] };
|
|
29
32
|
}
|
|
@@ -54,6 +57,28 @@ export function grade(scenario, transcript) {
|
|
|
54
57
|
violations.push(`forbidden tool called: ${forbidden}`);
|
|
55
58
|
}
|
|
56
59
|
}
|
|
60
|
+
if (criteria.forbidApproval) {
|
|
61
|
+
for (const tool of calledTools) {
|
|
62
|
+
if (tool.toLowerCase().includes("approve")) {
|
|
63
|
+
violations.push(`approval tool called: ${tool}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (criteria.failOnToolError && transcript.errorCount > 0) {
|
|
68
|
+
violations.push(`tool calls returned errors: ${transcript.errorCount}`);
|
|
69
|
+
}
|
|
70
|
+
const argumentText = transcript.steps.map((step) => JSON.stringify(step.args) ?? "").join("\n");
|
|
71
|
+
const compactArguments = argumentText.replace(/\s+/g, "");
|
|
72
|
+
for (const fragment of criteria.forbiddenArgumentText ?? []) {
|
|
73
|
+
if (compactArguments.includes(fragment.replace(/\s+/g, ""))) {
|
|
74
|
+
violations.push(`forbidden argument text: ${fragment}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const fragment of criteria.requiredArgumentText ?? []) {
|
|
78
|
+
if (!compactArguments.includes(fragment.replace(/\s+/g, ""))) {
|
|
79
|
+
violations.push(`argument text missing: ${fragment}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
57
82
|
const answer = transcript.finalAnswer.toLowerCase();
|
|
58
83
|
for (const fragment of criteria.answerMustInclude ?? []) {
|
|
59
84
|
if (!answer.includes(fragment.toLowerCase())) {
|
|
@@ -17,6 +17,13 @@ export interface EvalResult {
|
|
|
17
17
|
* a target surface is still missing.
|
|
18
18
|
*/
|
|
19
19
|
missingTools: string[];
|
|
20
|
+
/** Copied from the transcript so a saved artifact can be audited without the live run. */
|
|
21
|
+
skillId?: string;
|
|
22
|
+
journeyStage?: string;
|
|
23
|
+
journeyAction?: string;
|
|
24
|
+
surface?: WorkflowTranscript["surface"];
|
|
25
|
+
attribution?: WorkflowTranscript["attribution"];
|
|
26
|
+
capturedIds?: Record<string, string>;
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* Auth mode the eval used to reach the MCP surface.
|
|
@@ -42,6 +49,8 @@ export interface ModelScorecard {
|
|
|
42
49
|
fail: number;
|
|
43
50
|
clarified: number;
|
|
44
51
|
error: number;
|
|
52
|
+
/** Scenarios skipped because a required server profile is off. Not a pass. */
|
|
53
|
+
blocked: number;
|
|
45
54
|
/** pass / scenarios. */
|
|
46
55
|
successRate: number;
|
|
47
56
|
/** clarified / scenarios. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/eval/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE3F,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/eval/report.ts"],"names":[],"mappings":"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"}
|
package/dist/src/eval/report.js
CHANGED
|
@@ -22,6 +22,12 @@ export function assembleReport(input) {
|
|
|
22
22
|
errorCount: grade.errorCount,
|
|
23
23
|
driverError: transcript.driverError,
|
|
24
24
|
missingTools: (requiredByScenario.get(grade.scenarioId) ?? []).filter((t) => !advertised.has(t)),
|
|
25
|
+
skillId: transcript.skillId,
|
|
26
|
+
journeyStage: transcript.journeyStage,
|
|
27
|
+
journeyAction: transcript.journeyAction,
|
|
28
|
+
surface: transcript.surface,
|
|
29
|
+
attribution: transcript.attribution,
|
|
30
|
+
capturedIds: transcript.capturedIds,
|
|
25
31
|
}));
|
|
26
32
|
const models = input.drivers.map((driver) => {
|
|
27
33
|
const own = results.filter((r) => r.modelId === driver.id);
|
|
@@ -30,6 +36,7 @@ export function assembleReport(input) {
|
|
|
30
36
|
const fail = own.filter((r) => r.outcome === "fail").length;
|
|
31
37
|
const clarified = own.filter((r) => r.outcome === "clarified").length;
|
|
32
38
|
const error = own.filter((r) => r.outcome === "error").length;
|
|
39
|
+
const blocked = own.filter((r) => r.outcome === "blocked").length;
|
|
33
40
|
const withToolErrors = own.filter((r) => r.errorCount > 0).length;
|
|
34
41
|
const totalToolErrors = own.reduce((acc, r) => acc + r.errorCount, 0);
|
|
35
42
|
const rate = (n) => (scenarios === 0 ? 0 : Number((n / scenarios).toFixed(4)));
|
|
@@ -44,6 +51,7 @@ export function assembleReport(input) {
|
|
|
44
51
|
fail,
|
|
45
52
|
clarified,
|
|
46
53
|
error,
|
|
54
|
+
blocked,
|
|
47
55
|
successRate: rate(pass),
|
|
48
56
|
clarificationRate: rate(clarified),
|
|
49
57
|
editRate: rate(withToolErrors),
|
|
@@ -51,7 +59,9 @@ export function assembleReport(input) {
|
|
|
51
59
|
};
|
|
52
60
|
});
|
|
53
61
|
const control = models.find((m) => m.vendor === "deterministic");
|
|
54
|
-
const controlPass = control
|
|
62
|
+
const controlPass = control
|
|
63
|
+
? control.fail === 0 && control.error === 0 && control.blocked === 0 && control.scenarios > 0
|
|
64
|
+
: false;
|
|
55
65
|
const liveModelsEvaluated = models.filter((m) => m.vendor !== "deterministic" && m.scenarios > 0).length;
|
|
56
66
|
const requiredTools = corpusRequiredTools(input.corpus);
|
|
57
67
|
const unresolvedRequiredTools = requiredTools.filter((t) => !advertised.has(t));
|
|
@@ -103,13 +113,13 @@ export function renderMarkdown(report) {
|
|
|
103
113
|
lines.push("");
|
|
104
114
|
lines.push("## Per-model scorecard");
|
|
105
115
|
lines.push("");
|
|
106
|
-
lines.push("| Model | Vendor | Avail | Pass | Fail | Clarified | Error | Success | Clarify | Edit |");
|
|
107
|
-
lines.push("| --- | --- | :---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
116
|
+
lines.push("| Model | Vendor | Avail | Pass | Fail | Clarified | Error | Blocked | Success | Clarify | Edit |");
|
|
117
|
+
lines.push("| --- | --- | :---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
108
118
|
for (const m of report.models) {
|
|
109
|
-
lines.push(`| \`${m.id}\` | ${m.vendor} | ${m.available ? "yes" : "no"} | ${m.pass} | ${m.fail} | ${m.clarified} | ${m.error} | ${pct(m.successRate)} | ${pct(m.clarificationRate)} | ${pct(m.editRate)} |`);
|
|
119
|
+
lines.push(`| \`${m.id}\` | ${m.vendor} | ${m.available ? "yes" : "no"} | ${m.pass} | ${m.fail} | ${m.clarified} | ${m.error} | ${m.blocked} | ${pct(m.successRate)} | ${pct(m.clarificationRate)} | ${pct(m.editRate)} |`);
|
|
110
120
|
}
|
|
111
121
|
lines.push("");
|
|
112
|
-
const failures = report.results.filter((r) => r.outcome === "fail" || r.outcome === "error");
|
|
122
|
+
const failures = report.results.filter((r) => r.outcome === "fail" || r.outcome === "error" || r.outcome === "blocked");
|
|
113
123
|
if (failures.length > 0) {
|
|
114
124
|
lines.push("## Non-passing results");
|
|
115
125
|
lines.push("");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/eval/runner.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,UAAU,EAAkB,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAmC,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/eval/runner.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,UAAU,EAAkB,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAmC,MAAM,YAAY,CAAC;AAGzF;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4GD,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmE/E"}
|
package/dist/src/eval/runner.js
CHANGED
|
@@ -10,6 +10,7 @@ import { isOgcCorpus, isStandaloneCorpus, resolveCorpus } from "./corpus.js";
|
|
|
10
10
|
import { resolveDrivers } from "./drivers/index.js";
|
|
11
11
|
import { grade } from "./grade.js";
|
|
12
12
|
import { assembleReport } from "./report.js";
|
|
13
|
+
import { annotateTranscript, blockedTranscript, redactTranscript, unavailableProfiles } from "./workflow-corpus.js";
|
|
13
14
|
/** Truthy-flag parse shared with the driver opt-ins (0/false/no/off ⇒ false). */
|
|
14
15
|
function isTruthy(value) {
|
|
15
16
|
if (typeof value !== "string") {
|
|
@@ -110,10 +111,24 @@ export async function runEval(options = {}) {
|
|
|
110
111
|
inputSchema: t.inputSchema,
|
|
111
112
|
}));
|
|
112
113
|
const ctx = buildContext(surface.client, tools);
|
|
114
|
+
const profilesConfigured = env.HONUA_SERVER_PROFILES !== undefined;
|
|
115
|
+
const activeProfiles = (env.HONUA_SERVER_PROFILES ?? "")
|
|
116
|
+
.split(",")
|
|
117
|
+
.map((profile) => profile.trim())
|
|
118
|
+
.filter((profile) => profile.length > 0);
|
|
119
|
+
const advertised = new Set(tools.map((tool) => tool.name));
|
|
113
120
|
const graded = [];
|
|
114
121
|
for (const driver of drivers) {
|
|
115
122
|
for (const scenario of corpus) {
|
|
116
|
-
const
|
|
123
|
+
const missingProfiles = profilesConfigured ? unavailableProfiles(scenario, activeProfiles) : [];
|
|
124
|
+
const toolsPresent = scenario.criteria.requiredTools.every((tool) => advertised.has(tool));
|
|
125
|
+
// An unset HONUA_SERVER_PROFILES is unknown, not proof the profiles are off.
|
|
126
|
+
// Advertised tools are stronger evidence than a partial profile list.
|
|
127
|
+
const missing = toolsPresent ? [] : missingProfiles;
|
|
128
|
+
const raw = missing.length > 0
|
|
129
|
+
? blockedTranscript(driver.id, scenario, missing)
|
|
130
|
+
: await driver.runWorkflow(scenario, ctx);
|
|
131
|
+
const transcript = redactTranscript(annotateTranscript(raw, scenario, driver.vendor === "deterministic" ? "HARNESS_DRIVEN" : "MODEL_SELECTED"));
|
|
117
132
|
graded.push({ grade: grade(scenario, transcript), transcript });
|
|
118
133
|
}
|
|
119
134
|
}
|
package/dist/src/eval/types.d.ts
CHANGED
|
@@ -42,6 +42,19 @@ export interface WorkflowTranscript {
|
|
|
42
42
|
errorCount: number;
|
|
43
43
|
/** Driver-level failure (timeout, exception, model unavailable). */
|
|
44
44
|
driverError?: string;
|
|
45
|
+
/** Which surface produced the step. Docs lookups are `docs`; CLI-only steps are `cli`. */
|
|
46
|
+
surface?: "mcp" | "docs" | "cli";
|
|
47
|
+
/** Skill whose operator sentence was the prompt. */
|
|
48
|
+
skillId?: string;
|
|
49
|
+
/** Copied from the journey oracle after the run. The model never sees these. */
|
|
50
|
+
journeyStage?: string;
|
|
51
|
+
journeyAction?: string;
|
|
52
|
+
/** Who chose the tool call. Console approval stays harness-driven. */
|
|
53
|
+
attribution?: "MODEL_SELECTED" | "HARNESS_DRIVEN";
|
|
54
|
+
/** Ids only. Credential material is never stored here. */
|
|
55
|
+
capturedIds?: Record<string, string>;
|
|
56
|
+
/** Set when a required server profile is off. Grades as `blocked`. */
|
|
57
|
+
blockedReason?: string;
|
|
45
58
|
}
|
|
46
59
|
/** Declarative success criteria graded against a transcript. */
|
|
47
60
|
export interface SuccessCriteria {
|
|
@@ -69,6 +82,17 @@ export interface SuccessCriteria {
|
|
|
69
82
|
* transcript grades as `pass` and a completed transcript grades as `fail`.
|
|
70
83
|
*/
|
|
71
84
|
expectClarification?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Substrings that must not appear in any tool-argument JSON. Used to fail a
|
|
87
|
+
* model that sets `allowAnonymous` or proposes `visibility: public`.
|
|
88
|
+
*/
|
|
89
|
+
forbiddenArgumentText?: string[];
|
|
90
|
+
/** Substrings that must appear in the tool-argument JSON. */
|
|
91
|
+
requiredArgumentText?: string[];
|
|
92
|
+
/** Any tool whose name contains "approve" fails the scenario. */
|
|
93
|
+
forbidApproval?: boolean;
|
|
94
|
+
/** A tool result with isError fails the scenario. The control cannot pass on a failed call. */
|
|
95
|
+
failOnToolError?: boolean;
|
|
72
96
|
}
|
|
73
97
|
/** One GIS workflow in the held-out corpus. */
|
|
74
98
|
export interface Scenario {
|
|
@@ -95,9 +119,20 @@ export interface Scenario {
|
|
|
95
119
|
clarify?: {
|
|
96
120
|
question: string;
|
|
97
121
|
};
|
|
122
|
+
/** Skill file the prompt was taken from. Not shown to the model as an id. */
|
|
123
|
+
skillId?: string;
|
|
124
|
+
/** Journey stage id this scenario covers. Applied to the transcript after the run. */
|
|
125
|
+
journeyStage?: string;
|
|
126
|
+
/** Journey action id this scenario covers. */
|
|
127
|
+
journeyAction?: string;
|
|
128
|
+
/**
|
|
129
|
+
* MCP server profiles that must already be enabled. When any is missing the
|
|
130
|
+
* scenario is `blocked` and the model is not asked to call the tools.
|
|
131
|
+
*/
|
|
132
|
+
requiresServerProfiles?: string[];
|
|
98
133
|
}
|
|
99
|
-
/** Per-scenario grade for one model. */
|
|
100
|
-
export type GradeOutcome = "pass" | "fail" | "clarified" | "error";
|
|
134
|
+
/** Per-scenario grade for one model. `blocked` is a missing server profile, never a pass. */
|
|
135
|
+
export type GradeOutcome = "pass" | "fail" | "clarified" | "error" | "blocked";
|
|
101
136
|
export interface ScenarioGrade {
|
|
102
137
|
scenarioId: string;
|
|
103
138
|
modelId: string;
|