@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,455 @@
|
|
|
1
|
+
import { readNegotiatedProtocolVersion, resolveSuiteGitSha, } from "../provenance.js";
|
|
2
|
+
import { AUTH_ERROR_CODES, checkAuthContract, checkInvalidArgsContract, checkPaginationContract, } from "./contracts.js";
|
|
3
|
+
import { buildInvalidArgsInputs, buildRoundTripInputs, resolveRoundTripEnv } from "./fixtures.js";
|
|
4
|
+
import { checkConformance, checkWellFormed, validateAgainstSchema } from "./json-schema.js";
|
|
5
|
+
import { runDeepContracts } from "./lifecycle.js";
|
|
6
|
+
import { PLATFORM_FREE_DEGRADATIONS, buildReferenceToolLookup, loadSchemaFile, loadSchemaIndex, } from "./schema-index.js";
|
|
7
|
+
const STANDARD_SOURCE = "geospatial-mcp@54cbd49 (spec/schemas)";
|
|
8
|
+
/** Determine whether a discovered tool is safe to round-trip (read-only). */
|
|
9
|
+
export function isReadOnlyTool(tool) {
|
|
10
|
+
if (tool.annotations && typeof tool.annotations.readOnlyHint === "boolean") {
|
|
11
|
+
return tool.annotations.readOnlyHint;
|
|
12
|
+
}
|
|
13
|
+
const mutating = /(create|update|delete|publish|apply_.*write|execute|edit|deploy|cancel|propose)/i;
|
|
14
|
+
return !mutating.test(tool.name);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Connect an MCP client to the certified surface and produce a deterministic
|
|
18
|
+
* certification report. The client is already connected (offline mock upstream,
|
|
19
|
+
* a live `/mcp`, or the stdio proxy); certify never owns its lifecycle.
|
|
20
|
+
*/
|
|
21
|
+
export async function certify(options) {
|
|
22
|
+
const { client } = options;
|
|
23
|
+
const roundTripEnv = resolveRoundTripEnv(options.env);
|
|
24
|
+
const roundTripInputs = buildRoundTripInputs(roundTripEnv);
|
|
25
|
+
const index = loadSchemaIndex();
|
|
26
|
+
const referenceLookup = buildReferenceToolLookup(index);
|
|
27
|
+
const serverInfo = client.getServerVersion() ?? { name: "unknown", version: "0.0.0" };
|
|
28
|
+
const { tools: advertisedTools, pages: toolPages } = await listAllTools(client);
|
|
29
|
+
const { resources: advertisedResources, pages: resourcePages } = await listAllResources(client);
|
|
30
|
+
const prompts = await listPromptsSafely(client);
|
|
31
|
+
// The platform-free standalone target has no Honua publishing surface, so
|
|
32
|
+
// reference-shape tools (e.g. apply_style_preset) intentionally degrade their
|
|
33
|
+
// platform-bound required properties. Conformance is evaluated accordingly.
|
|
34
|
+
const platformFree = options.targetMode === "standalone";
|
|
35
|
+
const tools = [];
|
|
36
|
+
const advertisedNames = new Set();
|
|
37
|
+
for (const tool of advertisedTools) {
|
|
38
|
+
advertisedNames.add(tool.name);
|
|
39
|
+
tools.push(await certifyTool(client, tool, referenceLookup, roundTripInputs, platformFree));
|
|
40
|
+
}
|
|
41
|
+
const resources = advertisedResources.map((r) => ({
|
|
42
|
+
name: r.name,
|
|
43
|
+
uri: r.uri,
|
|
44
|
+
discovered: true,
|
|
45
|
+
}));
|
|
46
|
+
const contracts = [];
|
|
47
|
+
contracts.push(paginationCheck("tools", toolPages));
|
|
48
|
+
contracts.push(paginationCheck("resources", resourcePages));
|
|
49
|
+
collectOutputSchemaContracts(tools, contracts);
|
|
50
|
+
contracts.push(await runErrorShapeContract(client, advertisedTools, options.targetMode));
|
|
51
|
+
await runAuthContracts(options, advertisedTools, advertisedResources, contracts);
|
|
52
|
+
// Deep contracts: mutating round-trip, unauthenticated-write refusal, async job
|
|
53
|
+
// lifecycle, and query pagination. Safe by construction — mutations only run
|
|
54
|
+
// against the disposable offline mock or an explicit scratch target.
|
|
55
|
+
const deep = await runDeepContracts({
|
|
56
|
+
client,
|
|
57
|
+
advertisedToolNames: advertisedNames,
|
|
58
|
+
isDisposableBackend: options.backend === "mock-upstream",
|
|
59
|
+
connectUnauthenticated: options.connectUnauthenticated,
|
|
60
|
+
env: options.env,
|
|
61
|
+
});
|
|
62
|
+
contracts.push(...deep);
|
|
63
|
+
const knownGaps = collectKnownGaps(index, advertisedNames, referenceLookup);
|
|
64
|
+
const gitSha = resolveSuiteGitSha(options.env);
|
|
65
|
+
const provenance = {
|
|
66
|
+
suiteGitSha: gitSha.sha,
|
|
67
|
+
suiteGitShaSource: gitSha.source,
|
|
68
|
+
targetUrl: options.surface,
|
|
69
|
+
protocolVersion: readNegotiatedProtocolVersion(client),
|
|
70
|
+
toolCount: advertisedTools.length,
|
|
71
|
+
authMode: options.authMode ?? (options.backend === "mock-upstream" ? "bearer" : "unknown"),
|
|
72
|
+
};
|
|
73
|
+
return assembleReport({
|
|
74
|
+
serverInfo: serverInfo,
|
|
75
|
+
index,
|
|
76
|
+
options,
|
|
77
|
+
tools,
|
|
78
|
+
resources,
|
|
79
|
+
prompts,
|
|
80
|
+
contracts,
|
|
81
|
+
knownGaps,
|
|
82
|
+
provenance,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function listAllTools(client) {
|
|
86
|
+
const tools = [];
|
|
87
|
+
const pages = [];
|
|
88
|
+
let cursor;
|
|
89
|
+
do {
|
|
90
|
+
const resp = await client.listTools(cursor ? { cursor } : undefined);
|
|
91
|
+
tools.push(...resp.tools);
|
|
92
|
+
pages.push({ keys: resp.tools.map((t) => t.name), nextCursor: resp.nextCursor });
|
|
93
|
+
cursor = resp.nextCursor;
|
|
94
|
+
} while (cursor && pages.length < 100);
|
|
95
|
+
return { tools, pages };
|
|
96
|
+
}
|
|
97
|
+
async function listAllResources(client) {
|
|
98
|
+
const resources = [];
|
|
99
|
+
const pages = [];
|
|
100
|
+
let cursor;
|
|
101
|
+
do {
|
|
102
|
+
const resp = await client.listResources(cursor ? { cursor } : undefined);
|
|
103
|
+
resources.push(...resp.resources.map((r) => ({ name: r.name, uri: r.uri })));
|
|
104
|
+
pages.push({ keys: resp.resources.map((r) => r.uri), nextCursor: resp.nextCursor });
|
|
105
|
+
cursor = resp.nextCursor;
|
|
106
|
+
} while (cursor && pages.length < 100);
|
|
107
|
+
return { resources, pages };
|
|
108
|
+
}
|
|
109
|
+
async function listPromptsSafely(client) {
|
|
110
|
+
try {
|
|
111
|
+
const response = await client.listPrompts();
|
|
112
|
+
return response.prompts.map((p) => ({ name: p.name, discovered: true }));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function certifyTool(client, tool, referenceLookup, roundTripInputs, platformFree) {
|
|
119
|
+
const errors = [];
|
|
120
|
+
const notes = [];
|
|
121
|
+
const wellFormed = checkWellFormed(tool.inputSchema);
|
|
122
|
+
if (!wellFormed.wellFormed) {
|
|
123
|
+
errors.push(...wellFormed.errors.map((e) => `inputSchema not well-formed: ${e}`));
|
|
124
|
+
}
|
|
125
|
+
const hasOutputSchema = tool.outputSchema !== undefined && tool.outputSchema !== null;
|
|
126
|
+
if (hasOutputSchema) {
|
|
127
|
+
const outputWellFormed = checkWellFormed(tool.outputSchema);
|
|
128
|
+
if (!outputWellFormed.wellFormed) {
|
|
129
|
+
errors.push(...outputWellFormed.errors.map((e) => `outputSchema not well-formed: ${e}`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
notes.push("no structured output schema advertised");
|
|
134
|
+
}
|
|
135
|
+
const standardEntry = referenceLookup.get(tool.name);
|
|
136
|
+
let conformant = null;
|
|
137
|
+
if (standardEntry) {
|
|
138
|
+
const standardSchema = loadSchemaFile(standardEntry.schema);
|
|
139
|
+
if (wellFormed.wellFormed) {
|
|
140
|
+
const shedRequired = platformFree ? PLATFORM_FREE_DEGRADATIONS[tool.name] : undefined;
|
|
141
|
+
const result = checkConformance(tool.inputSchema, standardSchema, { shedRequired });
|
|
142
|
+
conformant = result.conformant;
|
|
143
|
+
if (!result.conformant) {
|
|
144
|
+
errors.push(...result.violations.map((v) => `conformance: ${v}`));
|
|
145
|
+
}
|
|
146
|
+
notes.push(...result.notes);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
conformant = false;
|
|
150
|
+
errors.push("conformance: skipped — inputSchema is not well-formed");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const readOnly = isReadOnlyTool(tool);
|
|
154
|
+
let roundTrip = "skipped";
|
|
155
|
+
let structuredOutputValidated = null;
|
|
156
|
+
const fixtureInput = roundTripInputs[tool.name];
|
|
157
|
+
if (readOnly && fixtureInput !== undefined) {
|
|
158
|
+
const outcome = await roundTripTool(client, tool, fixtureInput, errors, notes);
|
|
159
|
+
roundTrip = outcome.roundTrip;
|
|
160
|
+
structuredOutputValidated = outcome.structuredOutputValidated;
|
|
161
|
+
}
|
|
162
|
+
else if (!readOnly) {
|
|
163
|
+
notes.push("round-trip skipped: tool is not read-only");
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
notes.push("round-trip skipped: no fixture input");
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
name: tool.name,
|
|
170
|
+
discovered: true,
|
|
171
|
+
schemaValid: wellFormed.wellFormed,
|
|
172
|
+
hasOutputSchema,
|
|
173
|
+
standardName: standardEntry?.standardName ?? null,
|
|
174
|
+
conformant,
|
|
175
|
+
readOnly,
|
|
176
|
+
roundTrip,
|
|
177
|
+
structuredOutputValidated,
|
|
178
|
+
errors,
|
|
179
|
+
notes,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function roundTripTool(client, tool, input, errors, notes) {
|
|
183
|
+
try {
|
|
184
|
+
const result = (await client.callTool({ name: tool.name, arguments: input }));
|
|
185
|
+
if (result.isError) {
|
|
186
|
+
errors.push("round-trip: tool returned isError=true");
|
|
187
|
+
return { roundTrip: "failed", structuredOutputValidated: null };
|
|
188
|
+
}
|
|
189
|
+
if (tool.outputSchema && result.structuredContent !== undefined) {
|
|
190
|
+
const validation = validateAgainstSchema(tool.outputSchema, result.structuredContent);
|
|
191
|
+
if (!validation.valid) {
|
|
192
|
+
errors.push(...validation.errors.map((e) => `output-schema: structuredContent ${e}`));
|
|
193
|
+
return { roundTrip: "failed", structuredOutputValidated: false };
|
|
194
|
+
}
|
|
195
|
+
return { roundTrip: "passed", structuredOutputValidated: true };
|
|
196
|
+
}
|
|
197
|
+
if (tool.outputSchema && result.structuredContent === undefined) {
|
|
198
|
+
errors.push("output-schema: outputSchema advertised but no structuredContent returned");
|
|
199
|
+
return { roundTrip: "failed", structuredOutputValidated: false };
|
|
200
|
+
}
|
|
201
|
+
return { roundTrip: "passed", structuredOutputValidated: null };
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
errors.push(`round-trip: ${err instanceof Error ? err.message : String(err)}`);
|
|
205
|
+
return { roundTrip: "failed", structuredOutputValidated: null };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function paginationCheck(target, pages) {
|
|
209
|
+
const paginated = pages.length > 1 || (pages[0]?.nextCursor !== undefined && pages[0]?.nextCursor !== null);
|
|
210
|
+
if (!paginated) {
|
|
211
|
+
return {
|
|
212
|
+
contract: "list-pagination",
|
|
213
|
+
target,
|
|
214
|
+
status: "skipped",
|
|
215
|
+
detail: `${target} list is a single page (no nextCursor advertised)`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const outcome = checkPaginationContract(pages);
|
|
219
|
+
return {
|
|
220
|
+
contract: "list-pagination",
|
|
221
|
+
target,
|
|
222
|
+
status: outcome.ok ? "passed" : "failed",
|
|
223
|
+
detail: outcome.ok
|
|
224
|
+
? `${target} list paginated across ${pages.length} pages; nextCursor honored and terminated`
|
|
225
|
+
: outcome.errors.join("; "),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function collectOutputSchemaContracts(tools, contracts) {
|
|
229
|
+
for (const tool of tools) {
|
|
230
|
+
if (tool.structuredOutputValidated === null) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
contracts.push({
|
|
234
|
+
contract: "output-schema",
|
|
235
|
+
target: tool.name,
|
|
236
|
+
status: tool.structuredOutputValidated ? "passed" : "failed",
|
|
237
|
+
detail: tool.structuredOutputValidated
|
|
238
|
+
? "structuredContent validated against advertised outputSchema"
|
|
239
|
+
: "structuredContent did not validate against advertised outputSchema",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function runErrorShapeContract(client, advertised, targetMode) {
|
|
244
|
+
// The platform-free standalone surface is built on the high-level MCP server,
|
|
245
|
+
// which rejects invalid arguments with a JSON-RPC -32602 (InvalidParams) error
|
|
246
|
+
// BEFORE the tool body runs — a valid MCP-native structured error, but not the
|
|
247
|
+
// Honua GeoprocessingError envelope this contract asserts. That envelope is a
|
|
248
|
+
// Honua-operator richness a plain FeatureServer does not (and need not) provide,
|
|
249
|
+
// so we skip-with-reason rather than fail. Other targets are unaffected.
|
|
250
|
+
if (targetMode === "standalone") {
|
|
251
|
+
return {
|
|
252
|
+
contract: "error-shape",
|
|
253
|
+
target: "(standalone)",
|
|
254
|
+
status: "skipped",
|
|
255
|
+
detail: "standalone surface uses MCP-native input validation (JSON-RPC -32602 InvalidParams); the GeoprocessingError envelope is a Honua-operator surface feature",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const invalidInputs = buildInvalidArgsInputs();
|
|
259
|
+
const advertisedNames = new Set(advertised.map((t) => t.name));
|
|
260
|
+
const target = Object.keys(invalidInputs).find((name) => advertisedNames.has(name));
|
|
261
|
+
if (!target) {
|
|
262
|
+
return {
|
|
263
|
+
contract: "error-shape",
|
|
264
|
+
target: "(none)",
|
|
265
|
+
status: "skipped",
|
|
266
|
+
detail: "no advertised tool has an invalid-args fixture",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const result = (await client.callTool({ name: target, arguments: invalidInputs[target] }));
|
|
271
|
+
const outcome = checkInvalidArgsContract(result);
|
|
272
|
+
return contractFromOutcome("error-shape", target, outcome);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
return {
|
|
276
|
+
contract: "error-shape",
|
|
277
|
+
target,
|
|
278
|
+
status: "failed",
|
|
279
|
+
detail: `invalid arguments raised a protocol error instead of a structured tool error: ${err instanceof Error ? err.message : String(err)}`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function runAuthContracts(options, advertised, resources, contracts) {
|
|
284
|
+
if (!options.connectUnauthenticated) {
|
|
285
|
+
contracts.push({
|
|
286
|
+
contract: "auth-unauthenticated",
|
|
287
|
+
target: "tools/call",
|
|
288
|
+
status: "skipped",
|
|
289
|
+
detail: "target does not support an unauthenticated pass",
|
|
290
|
+
});
|
|
291
|
+
contracts.push({
|
|
292
|
+
contract: "auth-unauthenticated",
|
|
293
|
+
target: "resources/read",
|
|
294
|
+
status: "skipped",
|
|
295
|
+
detail: "target does not support an unauthenticated pass",
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
let unauth;
|
|
300
|
+
try {
|
|
301
|
+
unauth = await options.connectUnauthenticated();
|
|
302
|
+
// tools/call without credentials.
|
|
303
|
+
const toolTarget = advertised.find((t) => isReadOnlyTool(t)) ?? advertised[0];
|
|
304
|
+
if (toolTarget) {
|
|
305
|
+
const roundTripInputs = buildRoundTripInputs(resolveRoundTripEnv(options.env));
|
|
306
|
+
const args = roundTripInputs[toolTarget.name] ?? {};
|
|
307
|
+
contracts.push(await authCheckToolsCall(unauth, toolTarget.name, args));
|
|
308
|
+
}
|
|
309
|
+
// resources/read without credentials.
|
|
310
|
+
if (resources[0]) {
|
|
311
|
+
contracts.push(await authCheckResourcesRead(unauth, resources[0].uri));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
contracts.push({
|
|
316
|
+
contract: "auth-unauthenticated",
|
|
317
|
+
target: "tools/call",
|
|
318
|
+
status: "failed",
|
|
319
|
+
detail: `unauthenticated pass could not be established: ${err instanceof Error ? err.message : String(err)}`,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
if (unauth) {
|
|
324
|
+
await unauth.close().catch(() => { });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function authCheckToolsCall(client, name, args) {
|
|
329
|
+
try {
|
|
330
|
+
const result = await client.callTool({ name, arguments: args });
|
|
331
|
+
if (result.isError !== true) {
|
|
332
|
+
return {
|
|
333
|
+
contract: "auth-unauthenticated",
|
|
334
|
+
target: "tools/call",
|
|
335
|
+
status: "failed",
|
|
336
|
+
detail: `unauthenticated tools/call on ${name} returned a non-error result — auth boundary not enforced`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return contractFromOutcome("auth-unauthenticated", "tools/call", checkAuthContract(result));
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
// A structured McpError carrying an auth code also satisfies the contract.
|
|
343
|
+
return contractFromOutcome("auth-unauthenticated", "tools/call", checkAuthContract(err));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function authCheckResourcesRead(client, uri) {
|
|
347
|
+
try {
|
|
348
|
+
await client.readResource({ uri });
|
|
349
|
+
return {
|
|
350
|
+
contract: "auth-unauthenticated",
|
|
351
|
+
target: "resources/read",
|
|
352
|
+
status: "failed",
|
|
353
|
+
detail: `unauthenticated resources/read on ${uri} succeeded — auth boundary not enforced`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
return contractFromOutcome("auth-unauthenticated", "resources/read", checkAuthContract(err));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function contractFromOutcome(contract, target, outcome) {
|
|
361
|
+
return {
|
|
362
|
+
contract,
|
|
363
|
+
target,
|
|
364
|
+
status: outcome.ok ? "passed" : "failed",
|
|
365
|
+
detail: outcome.ok ? "contract satisfied" : outcome.errors.join("; "),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function collectKnownGaps(index, advertisedNames, referenceLookup) {
|
|
369
|
+
const gaps = [];
|
|
370
|
+
// Advertised names that resolve to a standard entry through the SDK-local
|
|
371
|
+
// reference aliases (e.g. honua_dry_run_plan → validate_plan) also count as
|
|
372
|
+
// covering that standard tool.
|
|
373
|
+
const coveredStandardNames = new Set();
|
|
374
|
+
for (const name of advertisedNames) {
|
|
375
|
+
const entry = referenceLookup.get(name);
|
|
376
|
+
if (entry) {
|
|
377
|
+
coveredStandardNames.add(entry.standardName);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for (const tool of index.tools) {
|
|
381
|
+
if (!coveredStandardNames.has(tool.standardName)) {
|
|
382
|
+
gaps.push({
|
|
383
|
+
kind: "standard-tool",
|
|
384
|
+
name: tool.standardName,
|
|
385
|
+
family: tool.family,
|
|
386
|
+
detail: tool.implementationStatus === "known-gap"
|
|
387
|
+
? "standard family not yet implemented as a discrete tool"
|
|
388
|
+
: `reference tool ${tool.referenceToolName ?? "(unnamed)"} not advertised by the certified surface`,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const name of advertisedNames) {
|
|
393
|
+
if (!referenceLookup.has(name)) {
|
|
394
|
+
gaps.push({
|
|
395
|
+
kind: "advertised-tool",
|
|
396
|
+
name,
|
|
397
|
+
detail: "advertised tool has no matching geospatial-mcp standard schema (validated for well-formedness only)",
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return gaps;
|
|
402
|
+
}
|
|
403
|
+
function assembleReport(args) {
|
|
404
|
+
const { serverInfo, index, options, tools, resources, prompts, contracts, knownGaps, provenance } = args;
|
|
405
|
+
const toolsSchemaValid = tools.filter((t) => t.schemaValid).length;
|
|
406
|
+
const conformanceChecked = tools.filter((t) => t.conformant !== null);
|
|
407
|
+
const toolsConformant = conformanceChecked.filter((t) => t.conformant === true).length;
|
|
408
|
+
const toolsRoundTripped = tools.filter((t) => t.roundTrip === "passed").length;
|
|
409
|
+
const toolsOutputValidated = tools.filter((t) => t.structuredOutputValidated === true).length;
|
|
410
|
+
const contractsChecked = contracts.filter((c) => c.status !== "skipped").length;
|
|
411
|
+
const contractsPassed = contracts.filter((c) => c.status === "passed").length;
|
|
412
|
+
const contractsFailed = contracts.filter((c) => c.status === "failed").length;
|
|
413
|
+
const contractsSkipped = contracts.filter((c) => c.status === "skipped").length;
|
|
414
|
+
const toolFailures = tools.reduce((acc, t) => acc + t.errors.length, 0);
|
|
415
|
+
const failures = toolFailures + contractsFailed;
|
|
416
|
+
const pass = failures === 0;
|
|
417
|
+
return {
|
|
418
|
+
schemaVersion: 2,
|
|
419
|
+
generatedAt: new Date().toISOString(),
|
|
420
|
+
server: serverInfo,
|
|
421
|
+
protocol: {
|
|
422
|
+
mcpTransport: options.targetMode === "stdio-proxy" ? "stdio→streamable-http" : "streamable-http",
|
|
423
|
+
honuaTransport: options.honuaTransport ?? null,
|
|
424
|
+
backend: options.backend,
|
|
425
|
+
targetMode: options.targetMode,
|
|
426
|
+
surface: options.surface,
|
|
427
|
+
},
|
|
428
|
+
standard: { source: STANDARD_SOURCE, indexDate: index.date, dialect: index.dialect },
|
|
429
|
+
provenance,
|
|
430
|
+
summary: {
|
|
431
|
+
pass,
|
|
432
|
+
toolsDiscovered: tools.length,
|
|
433
|
+
toolsSchemaValid,
|
|
434
|
+
toolsConformanceChecked: conformanceChecked.length,
|
|
435
|
+
toolsConformant,
|
|
436
|
+
toolsRoundTripped,
|
|
437
|
+
toolsOutputValidated,
|
|
438
|
+
resourcesDiscovered: resources.length,
|
|
439
|
+
promptsDiscovered: prompts.length,
|
|
440
|
+
contractsChecked,
|
|
441
|
+
contractsPassed,
|
|
442
|
+
contractsFailed,
|
|
443
|
+
contractsSkipped,
|
|
444
|
+
knownGaps: knownGaps.length,
|
|
445
|
+
failures,
|
|
446
|
+
},
|
|
447
|
+
tools,
|
|
448
|
+
resources,
|
|
449
|
+
prompts,
|
|
450
|
+
contracts,
|
|
451
|
+
knownGaps,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
// Re-export for artifact/report consumers.
|
|
455
|
+
export { AUTH_ERROR_CODES };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/certification/cli.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { runCertification, writeArtifacts } from "./run.js";
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
let artifactOnly = false;
|
|
8
|
+
// Default to the package root (two levels up from dist/src/certification/).
|
|
9
|
+
let outDir = fileURLToPath(new URL("../../../", import.meta.url));
|
|
10
|
+
let target;
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const arg = argv[i];
|
|
13
|
+
if (arg === "--artifact-only") {
|
|
14
|
+
artifactOnly = true;
|
|
15
|
+
}
|
|
16
|
+
else if (arg === "--out-dir") {
|
|
17
|
+
const value = argv[++i];
|
|
18
|
+
if (!value) {
|
|
19
|
+
throw new Error("--out-dir requires a directory argument");
|
|
20
|
+
}
|
|
21
|
+
outDir = resolve(value);
|
|
22
|
+
}
|
|
23
|
+
else if (arg === "--target") {
|
|
24
|
+
const value = argv[++i];
|
|
25
|
+
if (!value) {
|
|
26
|
+
throw new Error("--target requires a target mode (offline | remote | stdio-proxy | standalone)");
|
|
27
|
+
}
|
|
28
|
+
target = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { artifactOnly, outDir, ...(target ? { target } : {}) };
|
|
32
|
+
}
|
|
33
|
+
function artifactPaths(outDir) {
|
|
34
|
+
return {
|
|
35
|
+
json: resolve(outDir, "mcp-certification-results.json"),
|
|
36
|
+
markdown: resolve(outDir, "mcp-certification-results.md"),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
const { artifactOnly, outDir, target } = parseArgs(process.argv.slice(2));
|
|
41
|
+
const env = target ? { ...process.env, HONUA_MCP_CERT_TARGET: target } : process.env;
|
|
42
|
+
const report = await runCertification({ env });
|
|
43
|
+
mkdirSync(outDir, { recursive: true });
|
|
44
|
+
const paths = artifactPaths(outDir);
|
|
45
|
+
writeArtifacts(report, paths);
|
|
46
|
+
const s = report.summary;
|
|
47
|
+
const status = s.pass ? "PASS" : "FAIL";
|
|
48
|
+
process.stdout.write(`MCP certification ${status} [${report.protocol.targetMode}]: ${s.toolsConformant}/${s.toolsConformanceChecked} conformant, ` +
|
|
49
|
+
`${s.toolsOutputValidated} output-validated, ${s.contractsPassed}/${s.contractsChecked} contracts, ` +
|
|
50
|
+
`${s.contractsSkipped} skipped, ${s.knownGaps} known gaps, ${s.failures} failures.\n`);
|
|
51
|
+
const skipped = report.contracts.filter((c) => c.status === "skipped");
|
|
52
|
+
for (const c of skipped) {
|
|
53
|
+
process.stdout.write(` SKIP [${c.contract} / ${c.target}]: ${c.detail}\n`);
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write(`Artifacts: ${paths.json}\n ${paths.markdown}\n`);
|
|
56
|
+
if (!artifactOnly && !report.summary.pass) {
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
main().catch((err) => {
|
|
61
|
+
process.stderr.write(`Certification error: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, deterministic contract-check analyzers for MCP certification.
|
|
3
|
+
*
|
|
4
|
+
* These operate on values already obtained from the certified surface (tool
|
|
5
|
+
* results, thrown errors, paginated list responses) so they are unit-testable
|
|
6
|
+
* without any transport. The certifier does the I/O; these judge the shape.
|
|
7
|
+
*
|
|
8
|
+
* Contracts enforced (§P0.2):
|
|
9
|
+
* - output-schema : a tool's structuredContent validates against its outputSchema.
|
|
10
|
+
* - error-shape : invalid arguments yield an `isError` tool result carrying a
|
|
11
|
+
* structured, actionable code (validated against the vendored
|
|
12
|
+
* geoprocessing-error schema), never a protocol error.
|
|
13
|
+
* - auth : unauthenticated tools/call and resources/read surface a
|
|
14
|
+
* structured `unauthenticated` / `permission_denied` signal.
|
|
15
|
+
* - pagination : list `nextCursor` is honored and terminates.
|
|
16
|
+
*/
|
|
17
|
+
/** Machine codes that satisfy the unauthenticated / permission-denied contract. */
|
|
18
|
+
export declare const AUTH_ERROR_CODES: readonly ["unauthenticated", "permission_denied"];
|
|
19
|
+
/** geoprocessing-error `kind` values that denote an authorization failure. */
|
|
20
|
+
export declare const AUTH_ERROR_KINDS: readonly ["AuthorizationDenied", "Unauthenticated", "PermissionDenied"];
|
|
21
|
+
export interface StructuredErrorEnvelope {
|
|
22
|
+
code?: string;
|
|
23
|
+
error?: unknown;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a structured error envelope out of any of the places a surface may
|
|
27
|
+
* carry it: a tool result's `structuredContent`, an `McpError`'s `data`, or a
|
|
28
|
+
* bare geoprocessing-error object. Returns undefined when no structure is found.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractStructuredError(source: unknown): StructuredErrorEnvelope | undefined;
|
|
31
|
+
export interface ContractOutcome {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
/** Validate a value against the vendored geoprocessing-error schema. */
|
|
36
|
+
export declare function validateGeoprocessingError(error: unknown): ContractOutcome;
|
|
37
|
+
/**
|
|
38
|
+
* error-shape contract: an invalid-argument tool call must return an `isError`
|
|
39
|
+
* result whose structuredContent carries an actionable code and a
|
|
40
|
+
* geoprocessing-error-shaped `ValidationFailed` envelope.
|
|
41
|
+
*/
|
|
42
|
+
export declare function checkInvalidArgsContract(result: {
|
|
43
|
+
isError?: boolean;
|
|
44
|
+
structuredContent?: unknown;
|
|
45
|
+
}): ContractOutcome;
|
|
46
|
+
/**
|
|
47
|
+
* auth contract: an unauthenticated call must surface a structured
|
|
48
|
+
* `unauthenticated` / `permission_denied` signal — either as a code on the
|
|
49
|
+
* envelope or as an AuthorizationDenied geoprocessing-error kind.
|
|
50
|
+
*/
|
|
51
|
+
export declare function checkAuthContract(source: unknown): ContractOutcome;
|
|
52
|
+
/** Documented job states, ordered by lifecycle rank (terminal states are highest). */
|
|
53
|
+
export declare const JOB_STATE_RANK: Record<string, number>;
|
|
54
|
+
/** Observations gathered from a mutating insert→verify→update→verify→delete→verify round-trip. */
|
|
55
|
+
export interface FeatureRoundTripObservations {
|
|
56
|
+
/** Object id assigned by the insert (undefined ⇒ insert did not return one). */
|
|
57
|
+
insertedObjectId: number | undefined;
|
|
58
|
+
/** The inserted feature was found by a follow-up query. */
|
|
59
|
+
presentAfterInsert: boolean;
|
|
60
|
+
/** Attribute value read back after the update. */
|
|
61
|
+
attributeAfterUpdate: unknown;
|
|
62
|
+
/** Attribute value the update wrote (what `attributeAfterUpdate` must equal). */
|
|
63
|
+
expectedAttribute: unknown;
|
|
64
|
+
/** The feature was absent from a follow-up query after the delete. */
|
|
65
|
+
absentAfterDelete: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* mutating-round-trip contract: an insert must be observable, an update must be
|
|
69
|
+
* reflected, and a delete must remove the feature — the full edit lifecycle
|
|
70
|
+
* verified against the surface's own read path.
|
|
71
|
+
*/
|
|
72
|
+
export declare function checkFeatureRoundTrip(obs: FeatureRoundTripObservations): ContractOutcome;
|
|
73
|
+
/** Observations gathered from an async job execute→poll→result lifecycle. */
|
|
74
|
+
export interface JobLifecycleObservations {
|
|
75
|
+
/** Status returned synchronously by execute (e.g. "Submitted"). */
|
|
76
|
+
initialStatus: string;
|
|
77
|
+
/** Statuses observed by polling the job resource, in poll order. */
|
|
78
|
+
polledStatuses: string[];
|
|
79
|
+
/** Progress fractions observed alongside the polled statuses (0..1). */
|
|
80
|
+
progressFractions: number[];
|
|
81
|
+
/** The lifecycle reached a documented terminal state. */
|
|
82
|
+
finalStatus: string;
|
|
83
|
+
/** A result package was fetchable and reported ready once the job succeeded. */
|
|
84
|
+
resultReady: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* async-job-lifecycle contract: an execute → poll(honua://jobs/{id}) → fetch-result
|
|
88
|
+
* flow must follow the documented state machine — statuses are all recognized,
|
|
89
|
+
* rank never regresses, progress is monotonic within [0,1], the job terminates
|
|
90
|
+
* (Succeeded here), and the result package is retrievable.
|
|
91
|
+
*/
|
|
92
|
+
export declare function checkJobLifecycle(obs: JobLifecycleObservations): ContractOutcome;
|
|
93
|
+
export interface ListPage {
|
|
94
|
+
/** Distinct item identity keys observed on this page (e.g. tool names). */
|
|
95
|
+
keys: string[];
|
|
96
|
+
nextCursor: string | undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* pagination contract: following `nextCursor` yields additional distinct items,
|
|
100
|
+
* never repeats a cursor (terminates), and the final page has no cursor.
|
|
101
|
+
*/
|
|
102
|
+
export declare function checkPaginationContract(pages: ListPage[]): ContractOutcome;
|
|
103
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../../src/certification/contracts.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,mDAAoD,CAAC;AAElF,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,yEAA0E,CAAC;AAUxG,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,uBAAuB,GAAG,SAAS,CAgC3F;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wEAAwE;AACxE,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GAAG,eAAe,CA8BlB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAoBlE;AAED,sFAAsF;AACtF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOjD,CAAC;AAGF,kGAAkG;AAClG,MAAM,WAAW,4BAA4B;IAC3C,gFAAgF;IAChF,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,2DAA2D;IAC3D,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kDAAkD;IAClD,oBAAoB,EAAE,OAAO,CAAC;IAC9B,iFAAiF;IACjF,iBAAiB,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,4BAA4B,GAAG,eAAe,CAmBxF;AAED,6EAA6E;AAC7E,MAAM,WAAW,wBAAwB;IACvC,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,wEAAwE;IACxE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,wBAAwB,GAAG,eAAe,CAiChF;AAED,MAAM,WAAW,QAAQ;IACvB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,eAAe,CA6B1E"}
|