codex-plugin-doctor 1.59.0 → 1.61.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 CHANGED
@@ -81,6 +81,21 @@ Runtime MCP validation with `--runtime`:
81
81
  - optional runtime approval gating with a precomputed `doctor runtime-plan` digest
82
82
  - optional Docker isolation for local Node.js stdio servers with `--sandbox docker`
83
83
 
84
+ ### MCP Protocol Discovery
85
+
86
+ Inspect an HTTP MCP endpoint's advertised protocol versions using a bounded MCP
87
+ 2026-07-28 `server/discover` request:
88
+
89
+ ```bash
90
+ codex-plugin-doctor doctor discover https://mcp.example.com/mcp --allow-network
91
+ codex-plugin-doctor doctor discover https://mcp.example.com/mcp --allow-network --json
92
+ ```
93
+
94
+ This is an opt-in discovery-only check. A successful result validates the discovery
95
+ response, not full protocol conformance, tool behavior, or compatibility with a
96
+ particular client. Existing `check --runtime` remains on its 2025-11-25 baseline.
97
+ See [MCP Discovery](docs/guides/mcp-discovery.md) for consent, scope, and exit codes.
98
+
84
99
  ### Remote MCP Readiness
85
100
 
86
101
  Remote MCP runtime probing is disabled until you explicitly pass `--runtime --allow-network`. Loopback endpoints also require `--allow-local-network`:
@@ -131,21 +146,27 @@ codex-plugin-doctor registry preflight path/to/server.json --allow-network --req
131
146
  Preflight checks the exact npm package version, then the exact Registry version
132
147
  and latest Registry record on fixed public hosts. An existing exact Registry
133
148
  version is an immutable collision and must be replaced by a version bump. The
134
- command is advisory: it never authenticates, publishes, or changes npm or
135
- Registry records. See [MCP Registry Publication Preflight](./docs/architecture/mcp-registry-publication-preflight.md).
136
-
137
- ### Public Directory Submission Preflight
138
-
139
- Check a plugin package for deterministic public-directory submission issues without contacting a portal:
140
-
141
- ```bash
142
- codex-plugin-doctor doctor submission <path>
143
- codex-plugin-doctor doctor submission <path> --json
144
- codex-plugin-doctor doctor submission <path> --markdown
145
- codex-plugin-doctor doctor submission <path> --require-ready
146
- ```
147
-
148
- The preflight is offline and non-executing: it does not submit a package, make network requests, start MCP servers, verify domains, or handle OAuth credentials. Its automatic `status` is `pass` or `fail`; a passing automatic result remains `manual_review_required` until portal-only review is complete. It never claims directory acceptance. See [Public Directory Submission Preflight](./docs/architecture/public-directory-submission-preflight.md).
149
+ command is advisory: it never authenticates, publishes, or changes npm or
150
+ Registry records. See [MCP Registry Publication Preflight](./docs/architecture/mcp-registry-publication-preflight.md).
151
+
152
+ ### Public Directory Submission Preflight
153
+
154
+ Check a plugin package for deterministic public-directory submission issues without contacting a portal:
155
+
156
+ ```bash
157
+ codex-plugin-doctor doctor submission <path>
158
+ codex-plugin-doctor doctor submission <path> --json
159
+ codex-plugin-doctor doctor submission <path> --markdown
160
+ codex-plugin-doctor doctor submission <path> --require-ready
161
+ codex-plugin-doctor doctor submission archive <zip>
162
+ codex-plugin-doctor doctor submission archive <zip> --json --output submission-archive.json
163
+ codex-plugin-doctor doctor submission archive <zip> --markdown --output submission-archive.md
164
+ codex-plugin-doctor doctor submission archive <zip> --require-ready
165
+ ```
166
+
167
+ The preflight is offline and non-executing: it does not submit a package, make network requests, start MCP servers, verify domains, or handle OAuth credentials. Its automatic `status` is `pass` or `fail`; a passing automatic result remains `manual_review_required` until portal-only review is complete. It never claims directory acceptance. See [Public Directory Submission Preflight](./docs/architecture/public-directory-submission-preflight.md).
168
+
169
+ The archive variant accepts an existing skills-only ZIP and does not extract archive entries to disk, execute package code, or repair the archive. It reports malformed ZIP structures and deterministic package blockers; unsupported portal rules remain manual or `coverage: unavailable` rather than an automatic pass.
149
170
 
150
171
  Output formats:
151
172
 
@@ -493,9 +514,9 @@ jobs:
493
514
  runs-on: ubuntu-latest
494
515
  steps:
495
516
  - uses: actions/checkout@v5
496
- - uses: Esquetta/CodexPluginDoctor@v1.59.0
497
- with:
498
- version: "1.59.0"
517
+ - uses: Esquetta/CodexPluginDoctor@v1.61.0
518
+ with:
519
+ version: "1.61.0"
499
520
  path: .
500
521
  runtime: "true"
501
522
  policy: codex-publish
@@ -507,7 +528,7 @@ jobs:
507
528
  review-bundle-verify: "true"
508
529
  ```
509
530
 
510
- The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, `codex-plugin-doctor-action-manifest.json`, optional `codex-plugin-doctor.sarif`, optional validation corpus and quality metrics reports, optional `output-contract.json`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Set `submission: "true"` to add offline submission preflight reports; set `require-submission-ready: "true"` only with that opt-in to make automatic blockers fail the job. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, corpus quality regression gates, corpus and contract artifacts, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/guides/github-action.md).
531
+ The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, `codex-plugin-doctor-action-manifest.json`, optional `codex-plugin-doctor.sarif`, optional validation corpus and quality metrics reports, optional `output-contract.json`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Set `submission: "true"` to add offline submission preflight reports; set `require-submission-ready: "true"` only with that opt-in to make automatic blockers fail the job. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, corpus quality regression gates, corpus and contract artifacts, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/guides/github-action.md).
511
532
 
512
533
  To self-test this repository after cloning it:
513
534
 
@@ -8,6 +8,7 @@ const allowedRequestHeaders = new Set([
8
8
  "accept",
9
9
  "content-type",
10
10
  "last-event-id",
11
+ "mcp-method",
11
12
  "mcp-protocol-version",
12
13
  "mcp-session-id",
13
14
  "user-agent"
@@ -0,0 +1,23 @@
1
+ import { type RemoteLookup } from "./remote-network-policy.js";
2
+ import type { RemoteMcpRequest } from "./remote-mcp-probe.js";
3
+ import type { Finding } from "../domain/types.js";
4
+ export interface McpDiscoveryOptions {
5
+ allowNetwork?: boolean;
6
+ allowLocalNetwork?: boolean;
7
+ requestTimeoutMs?: number;
8
+ lookup?: RemoteLookup;
9
+ request?: RemoteMcpRequest;
10
+ }
11
+ export interface McpDiscoveryReport {
12
+ schemaVersion: 1;
13
+ requestedVersion: "2026-07-28";
14
+ scope: "discovery-only";
15
+ status: "discovered" | "unsupported" | "blocked" | "failed";
16
+ supportedVersions: string[];
17
+ findings: Finding[];
18
+ coverage: {
19
+ discovery: "pass" | "fail" | "skipped";
20
+ runtime: "not-tested";
21
+ };
22
+ }
23
+ export declare function discoverMcpServer(rawUrl: string, options?: McpDiscoveryOptions): Promise<McpDiscoveryReport>;
@@ -0,0 +1,148 @@
1
+ import { BoundedHttpError, requestBoundedHttp } from "./bounded-http-client.js";
2
+ import { RemoteNetworkPolicyError } from "./remote-network-policy.js";
3
+ import { inspectRemoteMcpUrl } from "./remote-url-policy.js";
4
+ import { packageVersion } from "../version.js";
5
+ const REQUESTED_VERSION = "2026-07-28";
6
+ const REQUEST_ID = "discover-1";
7
+ function finding(id, severity, message, impact, suggestedFix) {
8
+ return { id, severity, message, impact, suggestedFix };
9
+ }
10
+ function report(status, discovery, findings = [], supportedVersions = []) {
11
+ return { schemaVersion: 1, requestedVersion: REQUESTED_VERSION, scope: "discovery-only", status, supportedVersions, findings, coverage: { discovery, runtime: "not-tested" } };
12
+ }
13
+ function isPlainObject(value) {
14
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ }
16
+ function parseJsonObject(source) {
17
+ try {
18
+ const parsed = JSON.parse(source);
19
+ return isPlainObject(parsed) ? parsed : null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function mediaType(response) {
26
+ const contentType = response.headers["content-type"];
27
+ const value = Array.isArray(contentType) ? contentType[0] : contentType;
28
+ return typeof value === "string" ? value.split(";", 1)[0]?.trim().toLowerCase() ?? null : null;
29
+ }
30
+ function findSseResponse(body) {
31
+ const text = body.toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
32
+ let offset = 0;
33
+ while (offset < text.length) {
34
+ const boundary = text.indexOf("\n\n", offset);
35
+ if (boundary === -1)
36
+ return null;
37
+ const data = text.slice(offset, boundary).split("\n")
38
+ .filter((line) => line.startsWith("data:"))
39
+ .map((line) => line.slice(5).replace(/^ /, ""));
40
+ const candidate = data.length === 0 ? null : parseJsonObject(data.join("\n"));
41
+ if (candidate?.id === REQUEST_ID)
42
+ return candidate;
43
+ offset = boundary + 2;
44
+ }
45
+ return null;
46
+ }
47
+ function parseResponse(response) {
48
+ const contentType = mediaType(response);
49
+ return contentType === "application/json" ? parseJsonObject(response.body.toString("utf8"))
50
+ : contentType === "text/event-stream" ? findSseResponse(response.body)
51
+ : null;
52
+ }
53
+ function isValidVersion(value) {
54
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
55
+ return false;
56
+ const [year, month, day] = value.split("-").map(Number);
57
+ const date = new Date(Date.UTC(year, month - 1, day));
58
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
59
+ }
60
+ function validVersions(value) {
61
+ return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every(isValidVersion) && new Set(value).size === value.length;
62
+ }
63
+ function hasMatchingResponseEnvelope(message) {
64
+ return message.jsonrpc === "2.0" && message.id === REQUEST_ID
65
+ && Object.hasOwn(message, "result") !== Object.hasOwn(message, "error");
66
+ }
67
+ function validDiscoverResult(value) {
68
+ return isPlainObject(value) && value.resultType === "complete" && validVersions(value.supportedVersions)
69
+ && validCapabilities(value.capabilities) && typeof value.ttlMs === "number" && Number.isFinite(value.ttlMs) && value.ttlMs >= 0
70
+ && (value.cacheScope === "public" || value.cacheScope === "private") && validOptionalServerInfo(value);
71
+ }
72
+ function validCapabilities(value) {
73
+ return isPlainObject(value) && ["tools", "resources", "prompts", "completions", "logging", "tasks", "extensions"]
74
+ .every((name) => !Object.hasOwn(value, name) || isPlainObject(value[name]));
75
+ }
76
+ function validOptionalServerInfo(value) {
77
+ if (!Object.hasOwn(value, "_meta"))
78
+ return true;
79
+ if (!isPlainObject(value._meta))
80
+ return false;
81
+ const serverInfo = value._meta["io.modelcontextprotocol/serverInfo"];
82
+ return serverInfo === undefined || (isPlainObject(serverInfo) && typeof serverInfo.name === "string" && typeof serverInfo.version === "string");
83
+ }
84
+ function validError(value) {
85
+ return isPlainObject(value) && typeof value.code === "number" && Number.isFinite(value.code) && typeof value.message === "string";
86
+ }
87
+ function unsupportedVersions(error) {
88
+ if (!validError(error) || error.code !== -32022 || !isPlainObject(error.data))
89
+ return null;
90
+ return error.data.requested === REQUESTED_VERSION && validVersions(error.data.supported) ? error.data.supported : null;
91
+ }
92
+ function failureReport(id, message) {
93
+ return report("failed", "fail", [finding(id, "fail", message, "The discovery response cannot safely establish modern MCP discovery support.", "Verify the endpoint returns a valid server/discover response for the requested protocol version.")]);
94
+ }
95
+ function unsupportedReport(id, message, versions = []) {
96
+ return report("unsupported", "skipped", [finding(id, "warn", message, "Discovery-only probing cannot establish support for the requested MCP protocol version.", "Configure a server that supports the requested protocol version, then run discovery again.")], versions);
97
+ }
98
+ export async function discoverMcpServer(rawUrl, options = {}) {
99
+ if (!options.allowNetwork) {
100
+ return report("blocked", "skipped", [finding("plugin.discovery.network_not_approved", "warn", "Remote MCP discovery was not contacted because network access is not approved.", "Discovery would create outbound network traffic without explicit approval.", "Enable network access only after reviewing the endpoint.")]);
101
+ }
102
+ if (inspectRemoteMcpUrl(rawUrl).issues.length > 0) {
103
+ return report("blocked", "skipped", [finding("plugin.discovery.url.invalid", "warn", "The MCP discovery endpoint is unsafe or unsupported.", "Unsafe endpoint URLs can bypass network controls or expose credentials.", "Use an absolute HTTP or HTTPS endpoint without credentials, query parameters, fragments, or IP literals.")]);
104
+ }
105
+ const request = options.request ?? requestBoundedHttp;
106
+ const body = JSON.stringify({ jsonrpc: "2.0", id: REQUEST_ID, method: "server/discover", params: { _meta: {
107
+ "io.modelcontextprotocol/protocolVersion": REQUESTED_VERSION,
108
+ "io.modelcontextprotocol/clientInfo": { name: "Codex Plugin Doctor", version: packageVersion },
109
+ "io.modelcontextprotocol/clientCapabilities": {}
110
+ } } });
111
+ let response;
112
+ try {
113
+ response = await request(rawUrl, {
114
+ allowLocalNetwork: options.allowLocalNetwork, lookup: options.lookup, timeoutMs: options.requestTimeoutMs,
115
+ method: "POST", body,
116
+ headers: { Accept: "application/json, text/event-stream", "Content-Type": "application/json", "MCP-Protocol-Version": REQUESTED_VERSION, "Mcp-Method": "server/discover" },
117
+ stopAfter: (received) => findSseResponse(received) !== null
118
+ });
119
+ }
120
+ catch (error) {
121
+ if (error instanceof RemoteNetworkPolicyError) {
122
+ return report("blocked", "skipped", [finding("plugin.discovery.network.blocked", "warn", "The MCP discovery target is blocked by the remote network policy.", "Discovery could not safely reach the configured target.", "Use an endpoint permitted by the remote network policy.")]);
123
+ }
124
+ return failureReport(error instanceof BoundedHttpError && error.code === "REMOTE_HTTP_TIMEOUT" ? "plugin.discovery.transport.timeout" : "plugin.discovery.transport.failed", "The MCP discovery request did not complete within the configured transport bounds.");
125
+ }
126
+ if (response.statusCode === 401 || response.statusCode === 403) {
127
+ return unsupportedReport("plugin.discovery.authorization.required", "The MCP endpoint requires authorization before discovery can be assessed.");
128
+ }
129
+ const message = parseResponse(response);
130
+ if (!message || !hasMatchingResponseEnvelope(message))
131
+ return failureReport("plugin.discovery.response.invalid", "The MCP endpoint returned an invalid discovery response.");
132
+ if (Object.hasOwn(message, "error")) {
133
+ if (!validError(message.error))
134
+ return failureReport("plugin.discovery.response.error", "The MCP endpoint returned an invalid discovery error.");
135
+ const versions = unsupportedVersions(message.error);
136
+ if (versions !== null && response.statusCode === 400)
137
+ return unsupportedReport("plugin.discovery.protocol_version.unsupported", "The MCP endpoint does not support the requested protocol version.", versions);
138
+ if (message.error.code === -32601 && (response.statusCode === 200 || response.statusCode === 404))
139
+ return unsupportedReport("plugin.discovery.method.unsupported", "The MCP endpoint does not implement server discovery for this protocol version.");
140
+ return failureReport("plugin.discovery.response.error", "The MCP endpoint returned an unrecognized discovery error.");
141
+ }
142
+ if (response.statusCode !== 200 || !validDiscoverResult(message.result))
143
+ return failureReport("plugin.discovery.result.invalid", "The MCP endpoint returned an invalid discovery result.");
144
+ const versions = message.result.supportedVersions;
145
+ if (!versions.includes(REQUESTED_VERSION))
146
+ return unsupportedReport("plugin.discovery.protocol_version.inconsistent", "The MCP endpoint completed discovery without listing the requested protocol version.", versions);
147
+ return report("discovered", "pass", [], versions);
148
+ }
@@ -262,6 +262,68 @@ const publicSchemaDefinitions = [
262
262
  }
263
263
  }
264
264
  },
265
+ {
266
+ id: "doctor.submission.archive.json",
267
+ command: "codex-plugin-doctor doctor submission archive <zip> --json",
268
+ required: [
269
+ "schemaVersion",
270
+ "rulesetVersion",
271
+ "status",
272
+ "readiness",
273
+ "archive",
274
+ "summary",
275
+ "archiveChecks",
276
+ "submission",
277
+ "findings",
278
+ "coverage",
279
+ "manualChecklist"
280
+ ],
281
+ properties: {
282
+ rulesetVersion: { const: "openai-directory-archive-2026-08-23" },
283
+ status: { type: "string", enum: ["pass", "fail"] },
284
+ readiness: { type: "string", enum: ["blocked", "manual_review_required"] },
285
+ archive: {
286
+ type: "object",
287
+ required: ["fileName", "compressedBytes", "uncompressedBytes", "entryCount", "rootLayout"],
288
+ properties: {
289
+ fileName: { type: "string" },
290
+ compressedBytes: { type: "integer", minimum: 0 },
291
+ uncompressedBytes: { type: "integer", minimum: 0 },
292
+ entryCount: { type: "integer", minimum: 0 },
293
+ rootLayout: { type: "string", enum: ["archive-root", "single-top-level-directory", "unavailable"] }
294
+ },
295
+ additionalProperties: false
296
+ },
297
+ summary: {
298
+ type: "object",
299
+ required: ["passed", "warnings", "blockers", "manualChecks"],
300
+ properties: {
301
+ passed: { type: "integer", minimum: 0 },
302
+ warnings: { type: "integer", minimum: 0 },
303
+ blockers: { type: "integer", minimum: 0 },
304
+ manualChecks: { type: "integer", minimum: 0 }
305
+ },
306
+ additionalProperties: false
307
+ },
308
+ archiveChecks: { type: "array" },
309
+ submission: { type: ["object", "null"] },
310
+ findings: { type: "array" },
311
+ coverage: {
312
+ type: "array",
313
+ items: {
314
+ type: "object",
315
+ required: ["id", "status", "reason"],
316
+ properties: {
317
+ id: { type: "string", pattern: "^plugin\\.submission\\.archive\\." },
318
+ status: { type: "string", enum: ["automatic", "manual", "unavailable"] },
319
+ reason: { type: "string" }
320
+ },
321
+ additionalProperties: false
322
+ }
323
+ },
324
+ manualChecklist: { type: "array" }
325
+ }
326
+ },
265
327
  {
266
328
  id: "doctor.installed.check.json",
267
329
  command: "codex-plugin-doctor check --installed --json",
@@ -18,6 +18,7 @@ const topLevelCommands = [
18
18
  "config"
19
19
  ];
20
20
  const doctorCommands = ["submission"];
21
+ const submissionTargets = ["archive"];
21
22
  const submissionFlags = ["--json", "--markdown", "--output", "--require-ready"];
22
23
  const fishSubmissionCondition = "__fish_seen_subcommand_from doctor; and __fish_seen_subcommand_from submission";
23
24
  function bashCompletion() {
@@ -30,6 +31,7 @@ function bashCompletion() {
30
31
  "",
31
32
  ` local commands="${topLevelCommands.join(" ")}"`,
32
33
  ` local doctor_commands="${doctorCommands.join(" ")}"`,
34
+ ` local submission_targets="${submissionTargets.join(" ")}"`,
33
35
  ` local submission_flags="${submissionFlags.join(" ")}"`,
34
36
  "",
35
37
  " case \"${prev}\" in",
@@ -41,8 +43,25 @@ function bashCompletion() {
41
43
  " COMPREPLY=( $(compgen -W \"${doctor_commands}\" -- \"${cur}\") )",
42
44
  " return 0",
43
45
  " ;;",
46
+ " submission)",
47
+ " COMPREPLY=( $(compgen -W \"${submission_targets}\" -- \"${cur}\") )",
48
+ " return 0",
49
+ " ;;",
44
50
  " esac",
45
51
  "",
52
+ " if [[ ${COMP_WORDS[1]} == \"doctor\" && ${COMP_WORDS[2]} == \"submission\" && ${COMP_WORDS[3]} == \"archive\" ]]; then",
53
+ " case \"${cur}\" in",
54
+ " --*)",
55
+ " COMPREPLY=( $(compgen -W \"${submission_flags}\" -- \"${cur}\") )",
56
+ " return 0",
57
+ " ;;",
58
+ " *)",
59
+ " COMPREPLY=( $(compgen -f -- \"${cur}\") )",
60
+ " return 0",
61
+ " ;;",
62
+ " esac",
63
+ " fi",
64
+ "",
46
65
  " case \"${cur}\" in",
47
66
  " --*)",
48
67
  " if [[ ${COMP_WORDS[1]} == \"doctor\" && ${COMP_WORDS[2]} == \"submission\" ]]; then",
@@ -77,6 +96,8 @@ function zshCompletion() {
77
96
  "",
78
97
  " if [[ \"$words[2]\" == \"doctor\" && \"$words[3]\" == \"submission\" ]]; then",
79
98
  " _arguments -C \\",
99
+ " '3:archive target:(archive)' \\",
100
+ " '4:ZIP archive:_files' \\",
80
101
  " '*--json[Output as JSON]' \\",
81
102
  " '*--markdown[Output as Markdown]' \\",
82
103
  " '*--output[Write to file]:file:_files' \\",
@@ -108,6 +129,8 @@ function fishCompletion() {
108
129
  `complete -c codex-plugin-doctor -n "${fishSubmissionCondition}" -l markdown -d 'Output as Markdown'`,
109
130
  `complete -c codex-plugin-doctor -n "${fishSubmissionCondition}" -l output -d 'Write to file' -r`,
110
131
  `complete -c codex-plugin-doctor -n "${fishSubmissionCondition}" -l require-ready -d 'Fail when automatic checks are blocked'`,
132
+ `complete -c codex-plugin-doctor -n "${fishSubmissionCondition}; and __fish_seen_subcommand_from archive" -F`,
133
+ `complete -c codex-plugin-doctor -n "${fishSubmissionCondition}; and not __fish_seen_subcommand_from archive" -a "${submissionTargets.join(" ")}" -d 'ZIP archive'`,
111
134
  `complete -c codex-plugin-doctor -n "__fish_seen_subcommand_from doctor; and not __fish_seen_subcommand_from ${doctorCommands.join(" ")}" -a "${doctorCommands.join(" ")}"`,
112
135
  "complete -c codex-plugin-doctor -l runtime -d 'Enable runtime probes'",
113
136
  "complete -c codex-plugin-doctor -l policy -d 'Apply policy' -x -a 'codex-publish mcp-strict security'",
@@ -0,0 +1,24 @@
1
+ import { type SubmissionArchiveCoverage, type SubmissionArchiveFinding } from "./submission-archive-reader.js";
2
+ import { type SubmissionCheck, type SubmissionFinding, type SubmissionManualCheck, type SubmissionPreflightReport } from "./submission-preflight.js";
3
+ export type SubmissionArchiveRootLayout = "archive-root" | "single-top-level-directory" | "unavailable";
4
+ export interface SubmissionArchivePreflightReport {
5
+ schemaVersion: "1.0.0";
6
+ rulesetVersion: "openai-directory-archive-2026-08-23";
7
+ status: "pass" | "fail";
8
+ readiness: "blocked" | "manual_review_required";
9
+ archive: {
10
+ fileName: string;
11
+ compressedBytes: number;
12
+ uncompressedBytes: number;
13
+ entryCount: number;
14
+ rootLayout: SubmissionArchiveRootLayout;
15
+ };
16
+ summary: SubmissionPreflightReport["summary"];
17
+ archiveChecks: readonly SubmissionCheck[];
18
+ submission: SubmissionPreflightReport | null;
19
+ findings: readonly SubmissionArchiveFinding[] | readonly (SubmissionArchiveFinding | SubmissionFinding)[];
20
+ coverage: readonly SubmissionArchiveCoverage[];
21
+ manualChecklist: readonly SubmissionManualCheck[];
22
+ }
23
+ export declare function submissionArchiveExitCode(report: SubmissionArchivePreflightReport, requireReady: boolean): 0 | 1;
24
+ export declare function buildSubmissionArchivePreflight(archivePath: string): Promise<SubmissionArchivePreflightReport>;