@honua/mcp-server 0.0.10-alpha.0 → 0.1.0-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/README.md +259 -15
- package/certification/geospatial-mcp-schemas/PROVENANCE.md +41 -0
- package/certification/geospatial-mcp-schemas/README.md +90 -0
- package/certification/geospatial-mcp-schemas/common/geoprocessing-error.schema.json +45 -0
- package/certification/geospatial-mcp-schemas/conformance/manifest.schema.json +79 -0
- package/certification/geospatial-mcp-schemas/index.json +390 -0
- package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +20 -0
- package/certification/geospatial-mcp-schemas/resources/app-template.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/resources/artifact.schema.json +69 -0
- package/certification/geospatial-mcp-schemas/resources/deployment.schema.json +27 -0
- package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/resources/map-template.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/resources/provenance.schema.json +50 -0
- package/certification/geospatial-mcp-schemas/resources/published-service.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/resources/result-package.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/resources/style.schema.json +12 -0
- package/certification/geospatial-mcp-schemas/resources/theme.schema.json +12 -0
- package/certification/geospatial-mcp-schemas/resources/workspace.schema.json +63 -0
- package/certification/geospatial-mcp-schemas/tools/apply_style_preset.schema.json +27 -0
- package/certification/geospatial-mcp-schemas/tools/buffer_features.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
- package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
- package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
- package/certification/geospatial-mcp-schemas/tools/edit_features.schema.json +74 -0
- package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
- package/certification/geospatial-mcp-schemas/tools/export_dataset.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
- package/certification/geospatial-mcp-schemas/tools/geocode_addresses.schema.json +34 -0
- package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +40 -0
- package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
- package/certification/geospatial-mcp-schemas/tools/ingest_dataset.schema.json +45 -0
- package/certification/geospatial-mcp-schemas/tools/join_features.schema.json +73 -0
- package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
- package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
- package/certification/geospatial-mcp-schemas/tools/overlay_features.schema.json +53 -0
- package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
- package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
- package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
- package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
- package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
- package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
- package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
- package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +74 -0
- package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
- package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +60 -0
- package/certification/geospatial-mcp-schemas/tools/reproject_features.schema.json +49 -0
- package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
- package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
- package/certification/geospatial-mcp-schemas/tools/summarize_statistics.schema.json +71 -0
- package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
- package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
- package/dist/src/capability.d.ts +48 -0
- package/dist/src/capability.d.ts.map +1 -0
- package/dist/src/capability.js +44 -0
- package/dist/src/certification/census-data.d.ts +33 -0
- package/dist/src/certification/census-data.d.ts.map +1 -0
- package/dist/src/certification/census-data.js +506 -0
- package/dist/src/certification/census-fixture-client.d.ts +7 -0
- package/dist/src/certification/census-fixture-client.d.ts.map +1 -0
- package/dist/src/certification/census-fixture-client.js +226 -0
- package/dist/src/certification/certifier.d.ts +125 -0
- package/dist/src/certification/certifier.d.ts.map +1 -0
- package/dist/src/certification/certifier.js +455 -0
- package/dist/src/certification/cli.d.ts +3 -0
- package/dist/src/certification/cli.d.ts.map +1 -0
- package/dist/src/certification/cli.js +63 -0
- package/dist/src/certification/contracts.d.ts +103 -0
- package/dist/src/certification/contracts.d.ts.map +1 -0
- package/dist/src/certification/contracts.js +245 -0
- package/dist/src/certification/fixture-client.d.ts +4 -0
- package/dist/src/certification/fixture-client.d.ts.map +1 -0
- package/dist/src/certification/fixture-client.js +113 -0
- package/dist/src/certification/fixtures.d.ts +45 -0
- package/dist/src/certification/fixtures.d.ts.map +1 -0
- package/dist/src/certification/fixtures.js +92 -0
- package/dist/src/certification/json-schema.d.ts +71 -0
- package/dist/src/certification/json-schema.d.ts.map +1 -0
- package/dist/src/certification/json-schema.js +172 -0
- package/dist/src/certification/lifecycle.d.ts +14 -0
- package/dist/src/certification/lifecycle.d.ts.map +1 -0
- package/dist/src/certification/lifecycle.js +326 -0
- package/dist/src/certification/mock-upstream.d.ts +29 -0
- package/dist/src/certification/mock-upstream.d.ts.map +1 -0
- package/dist/src/certification/mock-upstream.js +96 -0
- package/dist/src/certification/operator-catalog.d.ts +90 -0
- package/dist/src/certification/operator-catalog.d.ts.map +1 -0
- package/dist/src/certification/operator-catalog.js +650 -0
- package/dist/src/certification/report.d.ts +4 -0
- package/dist/src/certification/report.d.ts.map +1 -0
- package/dist/src/certification/report.js +103 -0
- package/dist/src/certification/run.d.ts +28 -0
- package/dist/src/certification/run.d.ts.map +1 -0
- package/dist/src/certification/run.js +32 -0
- package/dist/src/certification/schema-index.d.ts +94 -0
- package/dist/src/certification/schema-index.d.ts.map +1 -0
- package/dist/src/certification/schema-index.js +109 -0
- package/dist/src/certification/target.d.ts +48 -0
- package/dist/src/certification/target.d.ts.map +1 -0
- package/dist/src/certification/target.js +219 -0
- package/dist/src/eval/cli.d.ts +3 -0
- package/dist/src/eval/cli.d.ts.map +1 -0
- package/dist/src/eval/cli.js +118 -0
- package/dist/src/eval/corpus.d.ts +34 -0
- package/dist/src/eval/corpus.d.ts.map +1 -0
- package/dist/src/eval/corpus.js +150 -0
- package/dist/src/eval/drivers/anthropic.d.ts +14 -0
- package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
- package/dist/src/eval/drivers/anthropic.js +120 -0
- package/dist/src/eval/drivers/bedrock.d.ts +54 -0
- package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
- package/dist/src/eval/drivers/bedrock.js +178 -0
- package/dist/src/eval/drivers/deterministic.d.ts +17 -0
- package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
- package/dist/src/eval/drivers/deterministic.js +64 -0
- package/dist/src/eval/drivers/index.d.ts +34 -0
- package/dist/src/eval/drivers/index.d.ts.map +1 -0
- package/dist/src/eval/drivers/index.js +74 -0
- package/dist/src/eval/drivers/openai.d.ts +14 -0
- package/dist/src/eval/drivers/openai.d.ts.map +1 -0
- package/dist/src/eval/drivers/openai.js +136 -0
- package/dist/src/eval/grade.d.ts +14 -0
- package/dist/src/eval/grade.d.ts.map +1 -0
- package/dist/src/eval/grade.js +75 -0
- package/dist/src/eval/northstar-corpus.d.ts +3 -0
- package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
- package/dist/src/eval/northstar-corpus.js +194 -0
- package/dist/src/eval/operator-corpus.d.ts +3 -0
- package/dist/src/eval/operator-corpus.d.ts.map +1 -0
- package/dist/src/eval/operator-corpus.js +193 -0
- package/dist/src/eval/report.d.ts +109 -0
- package/dist/src/eval/report.d.ts.map +1 -0
- package/dist/src/eval/report.js +133 -0
- package/dist/src/eval/runner.d.ts +28 -0
- package/dist/src/eval/runner.d.ts.map +1 -0
- package/dist/src/eval/runner.js +130 -0
- package/dist/src/eval/standalone-corpus.d.ts +3 -0
- package/dist/src/eval/standalone-corpus.d.ts.map +1 -0
- package/dist/src/eval/standalone-corpus.js +699 -0
- package/dist/src/eval/types.d.ts +118 -0
- package/dist/src/eval/types.d.ts.map +1 -0
- package/dist/src/eval/types.js +10 -0
- package/dist/src/helpers.d.ts +11 -0
- package/dist/src/helpers.d.ts.map +1 -1
- package/dist/src/helpers.js +22 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +50 -4
- package/dist/src/provenance.d.ts +44 -0
- package/dist/src/provenance.d.ts.map +1 -0
- package/dist/src/provenance.js +37 -0
- package/dist/src/proxy.d.ts +46 -0
- package/dist/src/proxy.d.ts.map +1 -0
- package/dist/src/proxy.js +124 -0
- package/dist/src/resources/styles.d.ts +29 -0
- package/dist/src/resources/styles.d.ts.map +1 -0
- package/dist/src/resources/styles.js +76 -0
- package/dist/src/styles.d.ts +112 -0
- package/dist/src/styles.d.ts.map +1 -0
- package/dist/src/styles.js +144 -0
- package/dist/src/tools/apply-style-preset.d.ts +27 -0
- package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
- package/dist/src/tools/apply-style-preset.js +41 -0
- package/dist/src/tools/count-features.d.ts.map +1 -1
- package/dist/src/tools/count-features.js +5 -4
- package/dist/src/tools/get-extent.d.ts.map +1 -1
- package/dist/src/tools/get-extent.js +6 -4
- package/dist/src/tools/get-style.d.ts +24 -0
- package/dist/src/tools/get-style.d.ts.map +1 -0
- package/dist/src/tools/get-style.js +39 -0
- package/dist/src/tools/list-services.d.ts.map +1 -1
- package/dist/src/tools/list-services.js +16 -1
- package/dist/src/tools/query-features.d.ts.map +1 -1
- package/dist/src/tools/query-features.js +4 -1
- package/package.json +28 -4
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NORTH-STAR corpus — the P1 gate measure (honua-server #1948, MCP initiative P1.7).
|
|
3
|
+
*
|
|
4
|
+
* These are the three canonical end-to-end workflows the Phase-1 gate is defined
|
|
5
|
+
* against: "all 3 north-star workflows pass via a vanilla MCP client". Encoding
|
|
6
|
+
* them as eval scenarios turns that gate into a mechanical pass/fail signal that
|
|
7
|
+
* runs on the exact same grading (required-tool selection, ordered subsequence,
|
|
8
|
+
* forbidden-tool avoidance, answer-substring evidence) as every other corpus.
|
|
9
|
+
*
|
|
10
|
+
* The tool names are the reference-implementation (`honua_*`) names for the
|
|
11
|
+
* standard geospatial-mcp tools (see geospatial-mcp `spec/schemas/index.json`).
|
|
12
|
+
* Some required tools are P1 deliverables that DO NOT yet ship on every surface:
|
|
13
|
+
*
|
|
14
|
+
* - `honua_publish_result` — standard `publish_result`, reference status
|
|
15
|
+
* `known-gap` (promote a job/result package to a
|
|
16
|
+
* hosted layer). Lands with P1.
|
|
17
|
+
* - `honua_apply_style_preset` — standard `apply_style_preset`, `known-gap`
|
|
18
|
+
* (styling a published layer). Lands with P1.
|
|
19
|
+
*
|
|
20
|
+
* Against a server that predates the P1 tools these scenarios are EXPECTED to
|
|
21
|
+
* fail — that failure is the gate signal, not a harness bug. The report makes it
|
|
22
|
+
* explicit which required tools a surface is missing:
|
|
23
|
+
* - `catalog.unresolvedRequiredTools` names every required tool the live
|
|
24
|
+
* `tools/list` did not advertise, corpus-wide.
|
|
25
|
+
* - each per-scenario `results[].missingTools` names the tools THAT scenario
|
|
26
|
+
* requires but the surface lacks (the gate dashboard, per workflow).
|
|
27
|
+
* The deterministic offline driver degrades HONESTLY: when its scripted ideal
|
|
28
|
+
* trajectory reaches a tool the surface does not advertise it records a
|
|
29
|
+
* `driverError` ("scripted tool not advertised …") and the scenario grades as
|
|
30
|
+
* `error`, never a silent pass. No fixture responses are faked to make an
|
|
31
|
+
* unbuilt tool look green.
|
|
32
|
+
*
|
|
33
|
+
* LIVE-GATED like the operator corpus: selected only via
|
|
34
|
+
* `HONUA_EVAL_CORPUS=northstar` (or `--corpus northstar`), and included in
|
|
35
|
+
* `all`. It is NOT part of the default analyst corpus, so the offline CI gate
|
|
36
|
+
* (`test:eval`, analyst-only) is untouched. Run it live against a P1 surface
|
|
37
|
+
* with `--driver bedrock` + `HONUA_MCP_REMOTE_URL=<server>/mcp` + credentials.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The generic mutation escape-hatch. Every north-star workflow must compose the
|
|
41
|
+
* structured lifecycle (plan → validate → execute → publish) rather than reach
|
|
42
|
+
* for `honua_propose_operation`, so forbidding it turns "did the client use the
|
|
43
|
+
* intended workflow?" into a graded criterion.
|
|
44
|
+
*/
|
|
45
|
+
const GENERIC_MUTATION_TOOL = "honua_propose_operation";
|
|
46
|
+
/** A small, embedded address CSV for the geocode → publish workflow (10 rows). */
|
|
47
|
+
const ADDRESS_CSV = [
|
|
48
|
+
"name,street,city,state,zip",
|
|
49
|
+
"City Hall,1 Dr Carlton B Goodlett Pl,San Francisco,CA,94102",
|
|
50
|
+
"Ferry Building,1 Ferry Building,San Francisco,CA,94111",
|
|
51
|
+
"Coit Tower,1 Telegraph Hill Blvd,San Francisco,CA,94133",
|
|
52
|
+
"Palace of Fine Arts,3601 Lyon St,San Francisco,CA,94123",
|
|
53
|
+
"SFMOMA,151 3rd St,San Francisco,CA,94103",
|
|
54
|
+
"Oracle Park,24 Willie Mays Plaza,San Francisco,CA,94107",
|
|
55
|
+
"Golden Gate Park,501 Stanyan St,San Francisco,CA,94117",
|
|
56
|
+
"Exploratorium,Pier 15 Embarcadero,San Francisco,CA,94111",
|
|
57
|
+
"Chase Center,1 Warriors Way,San Francisco,CA,94158",
|
|
58
|
+
"Sutro Baths,1004 Point Lobos Ave,San Francisco,CA,94121",
|
|
59
|
+
].join("\n");
|
|
60
|
+
/** Parsed address rows the geocoder batch call replays (without the header). */
|
|
61
|
+
const ADDRESS_ROWS = ADDRESS_CSV.split("\n")
|
|
62
|
+
.slice(1)
|
|
63
|
+
.map((line) => {
|
|
64
|
+
const [, street, city, state, zip] = line.split(",");
|
|
65
|
+
return `${street}, ${city}, ${state} ${zip}`;
|
|
66
|
+
});
|
|
67
|
+
/** The natural-language prompt for the CSV → point-layer workflow (CSV embedded). */
|
|
68
|
+
const CSV_GEOCODE_PROMPT = `Here is a CSV of addresses. Geocode them and publish a point layer from the results.
|
|
69
|
+
|
|
70
|
+
\`\`\`csv
|
|
71
|
+
${ADDRESS_CSV}
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
Batch-geocode every row, ingest the geocoded points as a dataset, and publish that dataset as a hosted point service. Do not reach for a raw propose_operation.`;
|
|
75
|
+
export const NORTHSTAR_CORPUS = [
|
|
76
|
+
{
|
|
77
|
+
id: "northstar-styled-map",
|
|
78
|
+
title: "Proximity analysis → published, styled map",
|
|
79
|
+
category: "north-star",
|
|
80
|
+
prompt: "Find all parcels within 500 m of a flood zone and produce a styled map of them. " +
|
|
81
|
+
"Resolve the parcels and flood-zone layers, plan and run the proximity analysis as a " +
|
|
82
|
+
"governed job, publish the result as a new layer, style it, and render a map. Use the " +
|
|
83
|
+
"structured analysis lifecycle — do not reach for a raw propose_operation.",
|
|
84
|
+
criteria: {
|
|
85
|
+
// Load-bearing arc of the workflow: ground the entities, execute the plan,
|
|
86
|
+
// publish the job artifact to a layer, and render a map of it.
|
|
87
|
+
requiredTools: ["honua_resolve_entity", "honua_execute_plan", "honua_publish_result", "honua_render_map"],
|
|
88
|
+
expectedToolSequence: ["honua_resolve_entity", "honua_execute_plan", "honua_publish_result", "honua_render_map"],
|
|
89
|
+
forbiddenTools: [GENERIC_MUTATION_TOOL],
|
|
90
|
+
},
|
|
91
|
+
script: [
|
|
92
|
+
{ tool: "honua_resolve_entity", args: { text: "parcels", entityType: "layer" } },
|
|
93
|
+
{ tool: "honua_resolve_entity", args: { text: "flood zones", entityType: "layer" } },
|
|
94
|
+
{
|
|
95
|
+
tool: "honua_plan_analysis",
|
|
96
|
+
args: { intent: "select parcels within 500 meters of a flood zone and produce a styled map" },
|
|
97
|
+
},
|
|
98
|
+
{ tool: "honua_validate_plan", args: { plan: { planId: "northstar-styled-map-plan" } } },
|
|
99
|
+
{ tool: "honua_execute_plan", args: { plan: { planId: "northstar-styled-map-plan" } } },
|
|
100
|
+
// P1 tools (reference known-gap today): promote the job artifact to a layer,
|
|
101
|
+
// then style it. On a pre-P1 surface these are unadvertised and the driver
|
|
102
|
+
// degrades to an honest driverError here rather than faking a response.
|
|
103
|
+
{ tool: "honua_publish_result", args: { sourceId: "northstar-styled-map-result", targetKind: "layer" } },
|
|
104
|
+
{
|
|
105
|
+
tool: "honua_apply_style_preset",
|
|
106
|
+
args: { mapPackageId: "northstar-styled-map-package", styleId: "flood-risk" },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
tool: "honua_render_map",
|
|
110
|
+
args: { layers: ["northstar-styled-map-layer"], bbox: [-122.55, 37.7, -122.35, 37.85] },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "northstar-csv-geocode-publish",
|
|
116
|
+
title: "CSV of addresses → geocoded, published point layer",
|
|
117
|
+
category: "north-star",
|
|
118
|
+
prompt: CSV_GEOCODE_PROMPT,
|
|
119
|
+
criteria: {
|
|
120
|
+
// The CSV → point-layer arc as the standard defines it: batch geocode →
|
|
121
|
+
// ingest the resulting points → publish the source table as a service.
|
|
122
|
+
requiredTools: ["honua_geocode_addresses", "honua_ingest_dataset", "honua_publish_service"],
|
|
123
|
+
expectedToolSequence: ["honua_geocode_addresses", "honua_ingest_dataset", "honua_publish_service"],
|
|
124
|
+
forbiddenTools: [GENERIC_MUTATION_TOOL],
|
|
125
|
+
},
|
|
126
|
+
script: [
|
|
127
|
+
{ tool: "honua_geocode_addresses", args: { addresses: ADDRESS_ROWS } },
|
|
128
|
+
{
|
|
129
|
+
tool: "honua_ingest_dataset",
|
|
130
|
+
args: {
|
|
131
|
+
format: "csv",
|
|
132
|
+
datasetName: "sf-landmarks",
|
|
133
|
+
data: ADDRESS_CSV,
|
|
134
|
+
addressColumn: "street",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
tool: "honua_publish_service",
|
|
139
|
+
args: {
|
|
140
|
+
connectionId: "default",
|
|
141
|
+
schema: "public",
|
|
142
|
+
table: "sf_landmarks",
|
|
143
|
+
layerName: "SF Landmarks",
|
|
144
|
+
geometryType: "point",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "northstar-buffer-stats-job",
|
|
151
|
+
title: "Buffer → intersect → summary statistics, run as a job",
|
|
152
|
+
category: "north-star",
|
|
153
|
+
prompt: "Buffer highways by 100 m, intersect the buffers with wetlands, and give me summary " +
|
|
154
|
+
"statistics for the overlap. Run it as a job and report progress. Ground the goal into " +
|
|
155
|
+
"an analysis plan, validate it, then execute it as a governed job and report the results " +
|
|
156
|
+
"the job produces. Do not reach for a raw propose_operation.",
|
|
157
|
+
criteria: {
|
|
158
|
+
// Plan entry → validate → execute-as-job. The answer must carry evidence
|
|
159
|
+
// that the JOB path was used and its results read back — kept loose (the
|
|
160
|
+
// job reference token, not exact statistic values) so live variance in the
|
|
161
|
+
// numbers does not flip the grade.
|
|
162
|
+
requiredTools: ["honua_plan_analysis", "honua_validate_plan", "honua_execute_plan"],
|
|
163
|
+
expectedToolSequence: [
|
|
164
|
+
"honua_ground_candidates",
|
|
165
|
+
"honua_plan_analysis",
|
|
166
|
+
"honua_validate_plan",
|
|
167
|
+
"honua_execute_plan",
|
|
168
|
+
],
|
|
169
|
+
forbiddenTools: [GENERIC_MUTATION_TOOL],
|
|
170
|
+
answerMustInclude: ["job"],
|
|
171
|
+
},
|
|
172
|
+
script: [
|
|
173
|
+
{
|
|
174
|
+
tool: "honua_ground_candidates",
|
|
175
|
+
args: {
|
|
176
|
+
goal: "buffer highways by 100 meters, intersect with wetlands, and summarize the overlap",
|
|
177
|
+
assumptionPolicy: "UseDefaults",
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
tool: "honua_plan_analysis",
|
|
182
|
+
args: { intent: "buffer highways by 100m, intersect with wetlands, and compute summary statistics" },
|
|
183
|
+
},
|
|
184
|
+
{ tool: "honua_validate_plan", args: { plan: { planId: "northstar-buffer-stats-plan" } } },
|
|
185
|
+
// Optional dry-run estimate before committing (reference advertises it with
|
|
186
|
+
// the same partial-plan input schema as validate_plan).
|
|
187
|
+
{ tool: "honua_dry_run_plan", args: { plan: { planId: "northstar-buffer-stats-plan" } } },
|
|
188
|
+
{
|
|
189
|
+
tool: "honua_execute_plan",
|
|
190
|
+
args: { plan: { planId: "northstar-buffer-stats-plan" }, idempotencyKey: "northstar-buffer-stats-1" },
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator-corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/operator-corpus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA2D3C,eAAO,MAAM,eAAe,EAAE,QAAQ,EAmJrC,CAAC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator-surface corpus for the cross-model eval (honua-server #1956).
|
|
3
|
+
*
|
|
4
|
+
* The analyst corpus in `corpus.ts` targets the SDK `@honua/mcp-server` ANALYST
|
|
5
|
+
* tools (count_features, describe_layer, query_features, …). This corpus instead
|
|
6
|
+
* exercises the LIVE honua-server OPERATOR MCP surface — the agent-native control
|
|
7
|
+
* plane published at `https://demo.honua.io/mcp` (MCP streamable-HTTP, protocol
|
|
8
|
+
* 2025-03-26, serverInfo `honua.operator.mcp`). Its tools are the grounding →
|
|
9
|
+
* clarify → plan → validate → dry-run → (gated) execute lifecycle plus read-only
|
|
10
|
+
* data tools (list layers, query features) and Pro tools (geocode, route).
|
|
11
|
+
*
|
|
12
|
+
* Select it with `HONUA_EVAL_CORPUS=operator` (see `resolveCorpus` in corpus.ts)
|
|
13
|
+
* and run live with `--driver bedrock` + `HONUA_MCP_REMOTE_URL=<demo>/mcp` +
|
|
14
|
+
* `HONUA_API_KEY=<demo key>`. The same grading runs for every driver, so the
|
|
15
|
+
* deterministic control and Bedrock-Claude are measured against identical
|
|
16
|
+
* criteria over the identical live catalog.
|
|
17
|
+
*
|
|
18
|
+
* SAFETY: these scenarios are READ-MOSTLY and reversible. The end-to-end planning
|
|
19
|
+
* scenarios stop at validate / dry-run and FORBID the mutating lifecycle tools
|
|
20
|
+
* (`honua_execute_plan`, `honua_propose_operation`) — on the Community-edition
|
|
21
|
+
* demo `honua_propose_operation` executes directly (no approval gate), so a
|
|
22
|
+
* correct agent must NOT call it for a read-only analysis task. Forbidding those
|
|
23
|
+
* tools turns "did the agent stay read-only?" into a graded criterion.
|
|
24
|
+
*
|
|
25
|
+
* Grading is deliberately tool-centric: required-tool selection, ordered
|
|
26
|
+
* subsequence, and forbidden-tool avoidance — the verifiable signals of "the
|
|
27
|
+
* client composed the right workflow". Answer-content checks are used only where
|
|
28
|
+
* a data value (a layer name, a geometry word) is near-certain to be echoed, so a
|
|
29
|
+
* non-pass means a real capability/composition gap rather than a phrasing
|
|
30
|
+
* artifact. Tools gated by edition entitlements (geocode/route) are scored on
|
|
31
|
+
* correct tool selection alone (see `operator-geocode-gap`).
|
|
32
|
+
*
|
|
33
|
+
* Dataset shapes resolve against the seeded Maui demo content: the published
|
|
34
|
+
* service `svc-publish-maui-buildings` (layer 13, Overture building footprints).
|
|
35
|
+
*/
|
|
36
|
+
/** Seeded demo service/layer the scenarios bind to (from honua_list_layers). */
|
|
37
|
+
const MAUI_BUILDINGS_SERVICE = "svc-publish-maui-buildings";
|
|
38
|
+
const MAUI_BUILDINGS_LAYER = 13;
|
|
39
|
+
/** Mutating lifecycle tools a read-only analysis agent must never call. */
|
|
40
|
+
const MUTATING_TOOLS = ["honua_execute_plan", "honua_propose_operation"];
|
|
41
|
+
/** A small, static, well-formed analysis plan reused by validate/dry-run. */
|
|
42
|
+
const SAMPLE_PLAN = {
|
|
43
|
+
planId: "eval-operator-plan-1",
|
|
44
|
+
intentId: "eval-operator-intent-1",
|
|
45
|
+
steps: [
|
|
46
|
+
{
|
|
47
|
+
stepId: "s1",
|
|
48
|
+
kind: "QueryFeatures",
|
|
49
|
+
inputs: { serviceId: MAUI_BUILDINGS_SERVICE, layerId: String(MAUI_BUILDINGS_LAYER) },
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
outputs: ["Scalar"],
|
|
53
|
+
};
|
|
54
|
+
export const OPERATOR_CORPUS = [
|
|
55
|
+
{
|
|
56
|
+
id: "operator-list-layers",
|
|
57
|
+
title: "Discover the published layers on the operator surface",
|
|
58
|
+
category: "discovery",
|
|
59
|
+
prompt: "I just connected to this Honua server. What map layers are published here that I could query or map? Just list what's available.",
|
|
60
|
+
criteria: {
|
|
61
|
+
requiredTools: ["honua_list_layers"],
|
|
62
|
+
answerMustInclude: ["maui"],
|
|
63
|
+
},
|
|
64
|
+
script: [{ tool: "honua_list_layers", args: {} }],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "operator-discover-then-query",
|
|
68
|
+
title: "Discover a layer, then query features from it",
|
|
69
|
+
category: "query",
|
|
70
|
+
prompt: "Find the Maui buildings layer on this server and show me a few of its building features so I can see what attributes they carry.",
|
|
71
|
+
criteria: {
|
|
72
|
+
requiredTools: ["honua_list_layers", "honua_query_features"],
|
|
73
|
+
expectedToolSequence: ["honua_list_layers", "honua_query_features"],
|
|
74
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
75
|
+
answerMustInclude: ["building"],
|
|
76
|
+
},
|
|
77
|
+
script: [
|
|
78
|
+
{ tool: "honua_list_layers", args: { filter: "maui-buildings" } },
|
|
79
|
+
{
|
|
80
|
+
tool: "honua_query_features",
|
|
81
|
+
args: { serviceId: MAUI_BUILDINGS_SERVICE, layerId: MAUI_BUILDINGS_LAYER, limit: 3 },
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "operator-ground-intent",
|
|
87
|
+
title: "Ground an ambiguous analysis goal to a workflow",
|
|
88
|
+
category: "grounding",
|
|
89
|
+
prompt: "I want to count how many Maui buildings fall within 100 meters of the coast. Help me get started — figure out what kind of workflow this is and what data and processes apply. Don't run anything yet.",
|
|
90
|
+
criteria: {
|
|
91
|
+
requiredTools: ["honua_ground_candidates"],
|
|
92
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
93
|
+
},
|
|
94
|
+
script: [
|
|
95
|
+
{
|
|
96
|
+
tool: "honua_ground_candidates",
|
|
97
|
+
args: {
|
|
98
|
+
goal: "count how many Maui buildings fall within 100 meters of the coast",
|
|
99
|
+
assumptionPolicy: "AskAlways",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "operator-clarify-loop",
|
|
106
|
+
title: "Ground then answer the clarification to refine intent",
|
|
107
|
+
category: "multi-step",
|
|
108
|
+
prompt: "I want to buffer the Maui buildings by 100 meters and count how many features fall in the buffered area. Start the grounding pass, and when the server asks a clarifying question, answer it (the buffer distance is 100 meters, the input is the maui-buildings layer). Stop once the intent is refined — do not execute.",
|
|
109
|
+
criteria: {
|
|
110
|
+
requiredTools: ["honua_ground_candidates", "honua_clarify_intent"],
|
|
111
|
+
expectedToolSequence: ["honua_ground_candidates", "honua_clarify_intent"],
|
|
112
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
113
|
+
},
|
|
114
|
+
script: [
|
|
115
|
+
{
|
|
116
|
+
tool: "honua_ground_candidates",
|
|
117
|
+
args: {
|
|
118
|
+
goal: "buffer the maui buildings by 100 meters and count how many fall in the area",
|
|
119
|
+
assumptionPolicy: "AskAlways",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
tool: "honua_clarify_intent",
|
|
124
|
+
args: {
|
|
125
|
+
goal: "buffer the maui buildings by 100 meters and count how many fall in the area",
|
|
126
|
+
intentId: "eval-operator-clarify-1",
|
|
127
|
+
response: { answers: { "param.distance": ["100"], "param.input": ["maui-buildings"] } },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "operator-plan-validate",
|
|
134
|
+
title: "End-to-end read-only planning: ground → plan → validate",
|
|
135
|
+
category: "multi-step",
|
|
136
|
+
prompt: "I need an executable analysis plan to count the Maui buildings, but I am NOT ready to run it. Ground the goal, compile it into an analysis plan, and validate that the plan is executable and what approvals it would need. Do not execute or propose any operation.",
|
|
137
|
+
criteria: {
|
|
138
|
+
requiredTools: ["honua_ground_candidates", "honua_plan_analysis", "honua_validate_plan"],
|
|
139
|
+
expectedToolSequence: ["honua_ground_candidates", "honua_plan_analysis", "honua_validate_plan"],
|
|
140
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
141
|
+
},
|
|
142
|
+
script: [
|
|
143
|
+
{ tool: "honua_ground_candidates", args: { goal: "count the maui buildings", assumptionPolicy: "UseDefaults" } },
|
|
144
|
+
{ tool: "honua_plan_analysis", args: { intent: "count the features in the maui-buildings layer" } },
|
|
145
|
+
{ tool: "honua_validate_plan", args: { plan: SAMPLE_PLAN } },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "operator-dry-run",
|
|
150
|
+
title: "Estimate a plan with a dry run before executing",
|
|
151
|
+
category: "analysis",
|
|
152
|
+
prompt: "Before I commit to running an analysis that queries the Maui buildings layer, give me a dry-run estimate of how long it would take, what kind of artifacts it would produce, and any side effects. Do not actually execute it.",
|
|
153
|
+
criteria: {
|
|
154
|
+
requiredTools: ["honua_dry_run_plan"],
|
|
155
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
156
|
+
},
|
|
157
|
+
script: [{ tool: "honua_dry_run_plan", args: { plan: SAMPLE_PLAN } }],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "operator-validate-package",
|
|
161
|
+
title: "Validate a query package against the governance contract",
|
|
162
|
+
category: "governance",
|
|
163
|
+
prompt: 'Run a read-only validation/review of a query package and tell me whether it is ready to execute and whether it needs approval. The package family is \'query\' and its entire payload is just this layer binding: { "serviceId": "svc-publish-maui-buildings", "layerId": 13 }. Validate it exactly as given — do not ask me for anything more.',
|
|
164
|
+
criteria: {
|
|
165
|
+
requiredTools: ["honua_validate_package"],
|
|
166
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
167
|
+
},
|
|
168
|
+
script: [
|
|
169
|
+
{
|
|
170
|
+
tool: "honua_validate_package",
|
|
171
|
+
args: {
|
|
172
|
+
packageFamily: "query",
|
|
173
|
+
packagePayload: { serviceId: MAUI_BUILDINGS_SERVICE, layerId: MAUI_BUILDINGS_LAYER },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "operator-geocode-gap",
|
|
180
|
+
title: "Route an address task to geocoding (entitlement-gated)",
|
|
181
|
+
category: "capability-gap",
|
|
182
|
+
prompt: "I have a street address — 'Kahului, Maui, Hawaii'. Get me the coordinates for it using this server. If the server can't do it, tell me honestly why instead of guessing coordinates.",
|
|
183
|
+
criteria: {
|
|
184
|
+
// Pro-gated on the Community demo: scored on correct tool SELECTION (the
|
|
185
|
+
// client must route an address task to the geocoder rather than fabricate
|
|
186
|
+
// coordinates). The honest gap report is asserted qualitatively, not via a
|
|
187
|
+
// brittle answer-substring match. forbiddenTools keeps it read-only.
|
|
188
|
+
requiredTools: ["honua_geocode_address"],
|
|
189
|
+
forbiddenTools: MUTATING_TOOLS,
|
|
190
|
+
},
|
|
191
|
+
script: [{ tool: "honua_geocode_address", args: { address: "Kahului, Maui, Hawaii", maxResults: 3 } }],
|
|
192
|
+
},
|
|
193
|
+
];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { SuiteProvenance } from "../provenance.js";
|
|
2
|
+
import type { ModelDriver, Scenario, ScenarioGrade, WorkflowTranscript } from "./types.js";
|
|
3
|
+
/** One graded (model, scenario) result plus a compact transcript view. */
|
|
4
|
+
export interface EvalResult {
|
|
5
|
+
scenarioId: string;
|
|
6
|
+
modelId: string;
|
|
7
|
+
outcome: ScenarioGrade["outcome"];
|
|
8
|
+
violations: string[];
|
|
9
|
+
toolsCalled: string[];
|
|
10
|
+
errorCount: number;
|
|
11
|
+
driverError?: string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Required tools THIS scenario needs that the live `tools/list` did not
|
|
14
|
+
* advertise — the per-workflow view of {@link EvalReport.catalog.unresolvedRequiredTools}.
|
|
15
|
+
* Empty when the surface advertises every tool the scenario requires. This is
|
|
16
|
+
* the P1 gate dashboard: it names, per north-star workflow, exactly which tools
|
|
17
|
+
* a target surface is still missing.
|
|
18
|
+
*/
|
|
19
|
+
missingTools: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Auth mode the eval used to reach the MCP surface.
|
|
23
|
+
*
|
|
24
|
+
* - `bearer` — `Authorization: Bearer <token>` (HONUA_MCP_AUTH_TOKEN).
|
|
25
|
+
* - `api-key` — `x-api-key: <key>` (HONUA_API_KEY).
|
|
26
|
+
* - `anonymous` — a live remote surface reached with NO credential.
|
|
27
|
+
* - `none` — the in-process offline fixture surface (no transport auth).
|
|
28
|
+
*
|
|
29
|
+
* Recorded in the artifact so a published live run proves it was authenticated
|
|
30
|
+
* (not run through a dev-auth bypass).
|
|
31
|
+
*/
|
|
32
|
+
export type AuthMode = "bearer" | "api-key" | "anonymous" | "none";
|
|
33
|
+
/** Per-model aggregate metrics — the measurable form of the north star. */
|
|
34
|
+
export interface ModelScorecard {
|
|
35
|
+
id: string;
|
|
36
|
+
/** Resolved model id for live drivers (attributable); omitted for the control. */
|
|
37
|
+
model?: string;
|
|
38
|
+
vendor: ModelDriver["vendor"];
|
|
39
|
+
available: boolean;
|
|
40
|
+
scenarios: number;
|
|
41
|
+
pass: number;
|
|
42
|
+
fail: number;
|
|
43
|
+
clarified: number;
|
|
44
|
+
error: number;
|
|
45
|
+
/** pass / scenarios. */
|
|
46
|
+
successRate: number;
|
|
47
|
+
/** clarified / scenarios. */
|
|
48
|
+
clarificationRate: number;
|
|
49
|
+
/** scenarios with >=1 erroring tool call / scenarios. */
|
|
50
|
+
editRate: number;
|
|
51
|
+
totalToolErrors: number;
|
|
52
|
+
}
|
|
53
|
+
export interface EvalReport {
|
|
54
|
+
schemaVersion: 4;
|
|
55
|
+
generatedAt: string;
|
|
56
|
+
/** Self-proving provenance: target, protocol, tool count, auth, suite git SHA. */
|
|
57
|
+
provenance: SuiteProvenance;
|
|
58
|
+
surface: {
|
|
59
|
+
backend: "fixture" | "live";
|
|
60
|
+
mcpTransport: string;
|
|
61
|
+
remoteUrl?: string;
|
|
62
|
+
/** How the eval authenticated to the surface (proves the run was authed). */
|
|
63
|
+
auth: AuthMode;
|
|
64
|
+
};
|
|
65
|
+
corpus: {
|
|
66
|
+
scenarios: number;
|
|
67
|
+
ids: string[];
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Catalog-coverage audit: how the corpus's required tools resolve against the
|
|
71
|
+
* LIVE `tools/list` the surface advertised. `unresolvedRequiredTools` names any
|
|
72
|
+
* required tool absent from the live catalog — so a scenario that fails does so
|
|
73
|
+
* for a real capability gap, not a silent name-resolution bug.
|
|
74
|
+
*/
|
|
75
|
+
catalog: {
|
|
76
|
+
advertisedToolCount: number;
|
|
77
|
+
requiredTools: string[];
|
|
78
|
+
unresolvedRequiredTools: string[];
|
|
79
|
+
};
|
|
80
|
+
models: ModelScorecard[];
|
|
81
|
+
results: EvalResult[];
|
|
82
|
+
summary: {
|
|
83
|
+
/** True iff the deterministic control passed every scenario (the CI gate). */
|
|
84
|
+
pass: boolean;
|
|
85
|
+
modelsEvaluated: number;
|
|
86
|
+
liveModelsEvaluated: number;
|
|
87
|
+
scenarios: number;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export interface AssembleInput {
|
|
91
|
+
backend: "fixture" | "live";
|
|
92
|
+
mcpTransport: string;
|
|
93
|
+
remoteUrl?: string | undefined;
|
|
94
|
+
auth: AuthMode;
|
|
95
|
+
/** Reproducibility fingerprint attached to the artifact (A5 provability). */
|
|
96
|
+
provenance: SuiteProvenance;
|
|
97
|
+
/** Tool names advertised by the live `tools/list` (for the catalog audit). */
|
|
98
|
+
advertisedTools: string[];
|
|
99
|
+
corpus: Scenario[];
|
|
100
|
+
drivers: ModelDriver[];
|
|
101
|
+
graded: {
|
|
102
|
+
grade: ScenarioGrade;
|
|
103
|
+
transcript: WorkflowTranscript;
|
|
104
|
+
}[];
|
|
105
|
+
}
|
|
106
|
+
export declare function assembleReport(input: AssembleInput): EvalReport;
|
|
107
|
+
/** Render a human-readable Markdown summary of the eval report. */
|
|
108
|
+
export declare function renderMarkdown(report: EvalReport): string;
|
|
109
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/eval/report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** Distinct required-tool names referenced across a corpus. */
|
|
2
|
+
function corpusRequiredTools(corpus) {
|
|
3
|
+
const names = new Set();
|
|
4
|
+
for (const scenario of corpus) {
|
|
5
|
+
for (const tool of scenario.criteria.requiredTools) {
|
|
6
|
+
names.add(tool);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return [...names].sort();
|
|
10
|
+
}
|
|
11
|
+
export function assembleReport(input) {
|
|
12
|
+
const advertised = new Set(input.advertisedTools);
|
|
13
|
+
// Per-scenario required tools, so each result can name the tools THIS workflow
|
|
14
|
+
// is missing from the surface (the per-workflow gate view).
|
|
15
|
+
const requiredByScenario = new Map(input.corpus.map((s) => [s.id, s.criteria.requiredTools]));
|
|
16
|
+
const results = input.graded.map(({ grade, transcript }) => ({
|
|
17
|
+
scenarioId: grade.scenarioId,
|
|
18
|
+
modelId: grade.modelId,
|
|
19
|
+
outcome: grade.outcome,
|
|
20
|
+
violations: grade.violations,
|
|
21
|
+
toolsCalled: transcript.steps.map((s) => s.tool),
|
|
22
|
+
errorCount: grade.errorCount,
|
|
23
|
+
driverError: transcript.driverError,
|
|
24
|
+
missingTools: (requiredByScenario.get(grade.scenarioId) ?? []).filter((t) => !advertised.has(t)),
|
|
25
|
+
}));
|
|
26
|
+
const models = input.drivers.map((driver) => {
|
|
27
|
+
const own = results.filter((r) => r.modelId === driver.id);
|
|
28
|
+
const scenarios = own.length;
|
|
29
|
+
const pass = own.filter((r) => r.outcome === "pass").length;
|
|
30
|
+
const fail = own.filter((r) => r.outcome === "fail").length;
|
|
31
|
+
const clarified = own.filter((r) => r.outcome === "clarified").length;
|
|
32
|
+
const error = own.filter((r) => r.outcome === "error").length;
|
|
33
|
+
const withToolErrors = own.filter((r) => r.errorCount > 0).length;
|
|
34
|
+
const totalToolErrors = own.reduce((acc, r) => acc + r.errorCount, 0);
|
|
35
|
+
const rate = (n) => (scenarios === 0 ? 0 : Number((n / scenarios).toFixed(4)));
|
|
36
|
+
return {
|
|
37
|
+
id: driver.id,
|
|
38
|
+
// The control has no underlying model; live drivers' id IS the resolved model.
|
|
39
|
+
...(driver.vendor === "deterministic" ? {} : { model: driver.id }),
|
|
40
|
+
vendor: driver.vendor,
|
|
41
|
+
available: driver.isAvailable(),
|
|
42
|
+
scenarios,
|
|
43
|
+
pass,
|
|
44
|
+
fail,
|
|
45
|
+
clarified,
|
|
46
|
+
error,
|
|
47
|
+
successRate: rate(pass),
|
|
48
|
+
clarificationRate: rate(clarified),
|
|
49
|
+
editRate: rate(withToolErrors),
|
|
50
|
+
totalToolErrors,
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
const control = models.find((m) => m.vendor === "deterministic");
|
|
54
|
+
const controlPass = control ? control.fail === 0 && control.error === 0 && control.scenarios > 0 : false;
|
|
55
|
+
const liveModelsEvaluated = models.filter((m) => m.vendor !== "deterministic" && m.scenarios > 0).length;
|
|
56
|
+
const requiredTools = corpusRequiredTools(input.corpus);
|
|
57
|
+
const unresolvedRequiredTools = requiredTools.filter((t) => !advertised.has(t));
|
|
58
|
+
return {
|
|
59
|
+
schemaVersion: 4,
|
|
60
|
+
generatedAt: new Date().toISOString(),
|
|
61
|
+
provenance: input.provenance,
|
|
62
|
+
surface: {
|
|
63
|
+
backend: input.backend,
|
|
64
|
+
mcpTransport: input.mcpTransport,
|
|
65
|
+
remoteUrl: input.remoteUrl,
|
|
66
|
+
auth: input.auth,
|
|
67
|
+
},
|
|
68
|
+
corpus: { scenarios: input.corpus.length, ids: input.corpus.map((s) => s.id) },
|
|
69
|
+
catalog: {
|
|
70
|
+
advertisedToolCount: input.advertisedTools.length,
|
|
71
|
+
requiredTools,
|
|
72
|
+
unresolvedRequiredTools,
|
|
73
|
+
},
|
|
74
|
+
models,
|
|
75
|
+
results,
|
|
76
|
+
summary: {
|
|
77
|
+
pass: controlPass,
|
|
78
|
+
modelsEvaluated: models.filter((m) => m.scenarios > 0).length,
|
|
79
|
+
liveModelsEvaluated,
|
|
80
|
+
scenarios: input.corpus.length,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Render a human-readable Markdown summary of the eval report. */
|
|
85
|
+
export function renderMarkdown(report) {
|
|
86
|
+
const lines = [];
|
|
87
|
+
const status = report.summary.pass ? "✅ PASS" : "❌ FAIL";
|
|
88
|
+
lines.push("# MCP Cross-Model Workflow Eval");
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push(`**Result (deterministic control):** ${status}`);
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push(`- Generated: \`${report.generatedAt}\``);
|
|
93
|
+
lines.push(`- MCP surface: \`${report.surface.backend}\` (transport: \`${report.surface.mcpTransport}\`${report.surface.remoteUrl ? `, remote: \`${report.surface.remoteUrl}\`` : ""})`);
|
|
94
|
+
lines.push(`- Auth mode: \`${report.surface.auth}\``);
|
|
95
|
+
lines.push(`- Negotiated protocol: \`${report.provenance.protocolVersion ?? "n/a"}\``);
|
|
96
|
+
lines.push(`- Suite git SHA: \`${report.provenance.suiteGitSha}\` (${report.provenance.suiteGitShaSource})`);
|
|
97
|
+
lines.push(`- Corpus: ${report.corpus.scenarios} GIS workflows`);
|
|
98
|
+
lines.push(`- Live models evaluated: ${report.summary.liveModelsEvaluated}`);
|
|
99
|
+
const unresolved = report.catalog.unresolvedRequiredTools;
|
|
100
|
+
lines.push(unresolved.length === 0
|
|
101
|
+
? `- Catalog coverage: all ${report.catalog.requiredTools.length} required tools resolve against the live catalog (${report.catalog.advertisedToolCount} advertised)`
|
|
102
|
+
: `- Catalog coverage: ⚠️ ${unresolved.length} required tool(s) NOT advertised by the surface: ${unresolved.map((t) => `\`${t}\``).join(", ")}`);
|
|
103
|
+
lines.push("");
|
|
104
|
+
lines.push("## Per-model scorecard");
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push("| Model | Vendor | Avail | Pass | Fail | Clarified | Error | Success | Clarify | Edit |");
|
|
107
|
+
lines.push("| --- | --- | :---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
108
|
+
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)} |`);
|
|
110
|
+
}
|
|
111
|
+
lines.push("");
|
|
112
|
+
const failures = report.results.filter((r) => r.outcome === "fail" || r.outcome === "error");
|
|
113
|
+
if (failures.length > 0) {
|
|
114
|
+
lines.push("## Non-passing results");
|
|
115
|
+
lines.push("");
|
|
116
|
+
for (const r of failures) {
|
|
117
|
+
lines.push(`- \`${r.modelId}\` × \`${r.scenarioId}\` — **${r.outcome}**`);
|
|
118
|
+
if (r.missingTools.length > 0) {
|
|
119
|
+
lines.push(` - missing required tool(s) on this surface: ${r.missingTools.map((t) => `\`${t}\``).join(", ")}`);
|
|
120
|
+
}
|
|
121
|
+
for (const v of r.violations) {
|
|
122
|
+
lines.push(` - ${v}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
lines.push("");
|
|
126
|
+
}
|
|
127
|
+
lines.push("> Live cross-model runs (Claude + GPT) are tracked under honua-io/honua-server#1956.");
|
|
128
|
+
lines.push("");
|
|
129
|
+
return `${lines.join("\n")}\n`;
|
|
130
|
+
}
|
|
131
|
+
function pct(rate) {
|
|
132
|
+
return `${(rate * 100).toFixed(0)}%`;
|
|
133
|
+
}
|