@bitkyc08/opencodex 2.19.0 → 2.20.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/gui/dist/assets/index-DF_UFrGS.css +1 -0
- package/gui/dist/assets/index-DSK3S5HY.js +76 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat.ts +367 -32
- package/src/adapters/registry.ts +144 -0
- package/src/chat/inbound.ts +13 -7
- package/src/cli/claude.ts +2 -1
- package/src/cli/combo.ts +3 -0
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/export-command.ts +2 -2
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +3 -2
- package/src/cli/lab.ts +135 -1
- package/src/cli/minimax.ts +491 -0
- package/src/cli/models-runtime.ts +22 -1
- package/src/cli/models.ts +67 -2
- package/src/cli/opencode.ts +2 -1
- package/src/cli/registry.ts +22 -2
- package/src/clients/config-export.ts +125 -7
- package/src/codex/app-server-processes.ts +57 -2
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/catalog/aggregation.ts +10 -1
- package/src/codex/catalog/effort.ts +15 -3
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +45 -5
- package/src/codex/catalog/sync.ts +74 -4
- package/src/codex/convergence.ts +2 -0
- package/src/combos/index.ts +1 -0
- package/src/combos/request.ts +30 -0
- package/src/combos/types.ts +6 -0
- package/src/config.ts +52 -0
- package/src/generated/compatibility-version.json +224 -76
- package/src/images/loop.ts +11 -1
- package/src/integrations/registry.ts +7 -0
- package/src/lab/conformance/jcs.ts +42 -2
- package/src/lab/conformance/negative-controls.ts +6 -2
- package/src/lab/conformance/runner.ts +16 -5
- package/src/lab/fabric/observe.ts +49 -14
- package/src/lab/index.ts +16 -0
- package/src/lab/ledger/purge.ts +152 -83
- package/src/lab/ledger/store.ts +168 -54
- package/src/lab/observe/from-conformance.ts +8 -6
- package/src/lab/observe/from-live.ts +8 -2
- package/src/lab/paths.ts +23 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +219 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/lab-activation.ts +109 -47
- package/src/lib/lab-live-pinned-sender.ts +16 -5
- package/src/lib/pinned-http.ts +70 -16
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/upstream-reachability.ts +4 -0
- package/src/lib/windows-elevation.ts +10 -1
- package/src/providers/derive.ts +24 -4
- package/src/providers/registry.ts +7 -3
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +143 -0
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/reasoning-effort.ts +27 -1
- package/src/router.ts +23 -6
- package/src/routing/capability.ts +4 -2
- package/src/routing/compatibility/behavior.ts +5 -1
- package/src/server/adapter-resolve.ts +2 -32
- package/src/server/auth-cors.ts +8 -0
- package/src/server/chat-completions.ts +74 -36
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +371 -0
- package/src/server/management/combo-routes.ts +16 -2
- package/src/server/management/config-routes.ts +6 -4
- package/src/server/management/context.ts +17 -0
- package/src/server/management/lab-routes.ts +181 -19
- package/src/server/management/model-routes.ts +76 -2
- package/src/server/management/model-rows.ts +8 -0
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +76 -4
- package/src/server/management/system-restart.ts +4 -2
- package/src/server/management/system-routes.ts +38 -0
- package/src/server/relay.ts +17 -3
- package/src/server/responses/compact.ts +4 -1
- package/src/server/responses/core.ts +257 -46
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/fetch-helpers.ts +35 -4
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +16 -2
- package/src/server/responses/terminal-guard.ts +1 -1
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses.ts +17 -2
- package/src/types.ts +66 -4
- package/src/update/index.ts +6 -5
- package/src/update/job.ts +5 -6
- package/src/update/notify.ts +5 -3
- package/src/usage/log.ts +11 -1
- package/gui/dist/assets/index-CQ7bIKee.css +0 -1
- package/gui/dist/assets/index-D_JUZLEC.js +0 -76
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { CompatibilityVerdict, EvidenceLayer } from "../constants";
|
|
2
|
+
|
|
3
|
+
export const PUBLIC_EVIDENCE_BUNDLE_SCHEMA_VERSION = "public_evidence_bundle_v1" as const;
|
|
4
|
+
export const PUBLIC_EXPORT_POLICY_VERSION = "public_export_policy_v1" as const;
|
|
5
|
+
export const PUBLIC_EVIDENCE_REVOCATION_SCHEMA_VERSION = "public_evidence_revocation_v1" as const;
|
|
6
|
+
export const PUBLIC_ROUTE_REGISTRY_SCHEMA_VERSION = "public_route_registry_v1" as const;
|
|
7
|
+
|
|
8
|
+
export const PUBLIC_ADAPTER_FAMILIES = [
|
|
9
|
+
"openai-responses",
|
|
10
|
+
"openai-chat",
|
|
11
|
+
"anthropic-messages",
|
|
12
|
+
] as const;
|
|
13
|
+
export type PublicAdapterFamily = (typeof PUBLIC_ADAPTER_FAMILIES)[number];
|
|
14
|
+
|
|
15
|
+
export interface PublicRouteRegistryEntryV1 {
|
|
16
|
+
providerId: string;
|
|
17
|
+
modelId: string;
|
|
18
|
+
adapterFamilies: PublicAdapterFamily[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PublicRouteRegistryManifestV1 {
|
|
22
|
+
schemaVersion: typeof PUBLIC_ROUTE_REGISTRY_SCHEMA_VERSION;
|
|
23
|
+
registryVersion: string;
|
|
24
|
+
sourceCommit: string;
|
|
25
|
+
entries: PublicRouteRegistryEntryV1[];
|
|
26
|
+
manifestDigest: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PublicProtocolSubjectV1 {
|
|
30
|
+
subjectKind: "protocol";
|
|
31
|
+
compatibilityVersion: string;
|
|
32
|
+
adapterFamily: PublicAdapterFamily;
|
|
33
|
+
inboundProtocol: string;
|
|
34
|
+
upstreamProtocol: string;
|
|
35
|
+
surface: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PublicRouteSubjectV1 {
|
|
39
|
+
subjectKind: "route";
|
|
40
|
+
providerId: string;
|
|
41
|
+
modelId: string;
|
|
42
|
+
adapterFamily: PublicAdapterFamily;
|
|
43
|
+
compatibilityVersion: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PublicTaskSubjectV1 {
|
|
47
|
+
subjectKind: "task";
|
|
48
|
+
route: PublicRouteSubjectV1;
|
|
49
|
+
taskClassId: string;
|
|
50
|
+
taskClassVersion: string;
|
|
51
|
+
taskFixtureDigest: string;
|
|
52
|
+
verifierManifestDigest: string;
|
|
53
|
+
fabricCompatibilityVersion: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type PublicEvidenceSubjectV1 =
|
|
57
|
+
| PublicProtocolSubjectV1
|
|
58
|
+
| PublicRouteSubjectV1
|
|
59
|
+
| PublicTaskSubjectV1;
|
|
60
|
+
|
|
61
|
+
export interface PublicAssertionSummaryV1 {
|
|
62
|
+
id: string;
|
|
63
|
+
required: boolean;
|
|
64
|
+
passed: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface PublicIncidentRefV1 {
|
|
68
|
+
corpusId: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PublicEvidenceRecordV1 {
|
|
72
|
+
recordId: string;
|
|
73
|
+
subjectId: string;
|
|
74
|
+
evidenceLayer: EvidenceLayer;
|
|
75
|
+
suiteId: string;
|
|
76
|
+
suiteVersion: string;
|
|
77
|
+
scenarioId: string;
|
|
78
|
+
scenarioVersion: string;
|
|
79
|
+
verdict: CompatibilityVerdict;
|
|
80
|
+
observedDayUtc: string;
|
|
81
|
+
subject: PublicEvidenceSubjectV1;
|
|
82
|
+
assertions: PublicAssertionSummaryV1[];
|
|
83
|
+
incidentRefs?: PublicIncidentRefV1[];
|
|
84
|
+
artifactRefs?: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PublicArtifactV1 {
|
|
88
|
+
artifactId: string;
|
|
89
|
+
artifactClass: string;
|
|
90
|
+
mediaType: string;
|
|
91
|
+
byteCount: number;
|
|
92
|
+
contentBase64: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface PublicPublisherV1 {
|
|
96
|
+
algorithm: "ed25519";
|
|
97
|
+
keyId: string;
|
|
98
|
+
publicKey: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface PublicBundleSignatureV1 {
|
|
102
|
+
algorithm: "ed25519";
|
|
103
|
+
signedDigest: string;
|
|
104
|
+
signature: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface PublicEvidenceBundleUnsignedV1 {
|
|
108
|
+
schemaVersion: typeof PUBLIC_EVIDENCE_BUNDLE_SCHEMA_VERSION;
|
|
109
|
+
exportPolicyVersion: typeof PUBLIC_EXPORT_POLICY_VERSION;
|
|
110
|
+
bundleId: string;
|
|
111
|
+
createdDayUtc: string;
|
|
112
|
+
publisher: PublicPublisherV1;
|
|
113
|
+
records: PublicEvidenceRecordV1[];
|
|
114
|
+
artifacts: PublicArtifactV1[];
|
|
115
|
+
bundleDigest: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface PublicEvidenceBundleV1 extends PublicEvidenceBundleUnsignedV1 {
|
|
119
|
+
signature: PublicBundleSignatureV1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface PublicEvidencePreviewBundleV1 {
|
|
123
|
+
schemaVersion: typeof PUBLIC_EVIDENCE_BUNDLE_SCHEMA_VERSION;
|
|
124
|
+
exportPolicyVersion: typeof PUBLIC_EXPORT_POLICY_VERSION;
|
|
125
|
+
createdDayUtc: string;
|
|
126
|
+
records: PublicEvidenceRecordV1[];
|
|
127
|
+
artifacts: PublicArtifactV1[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type PublicRevocationReasonV1 =
|
|
131
|
+
| "publisher_retracted"
|
|
132
|
+
| "privacy_retraction"
|
|
133
|
+
| "evidence_invalidated"
|
|
134
|
+
| "superseded";
|
|
135
|
+
|
|
136
|
+
export interface PublicRevocationTargetV1 {
|
|
137
|
+
kind: "bundle" | "record";
|
|
138
|
+
id: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface PublicEvidenceRevocationV1 {
|
|
142
|
+
schemaVersion: typeof PUBLIC_EVIDENCE_REVOCATION_SCHEMA_VERSION;
|
|
143
|
+
revocationId: string;
|
|
144
|
+
issuedDayUtc: string;
|
|
145
|
+
publisher: PublicPublisherV1;
|
|
146
|
+
targets: PublicRevocationTargetV1[];
|
|
147
|
+
reason: PublicRevocationReasonV1;
|
|
148
|
+
signature: PublicBundleSignatureV1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type PublicRevocationVerificationResult =
|
|
152
|
+
| { status: "cryptographically_valid"; revocation: PublicEvidenceRevocationV1 }
|
|
153
|
+
| { status: "schema_rejected" | "digest_invalid" | "signature_invalid" | "publisher_mismatch" | "unknown_target"; detail?: string };
|
|
154
|
+
|
|
155
|
+
export interface CommunityEvidenceSummaryV1 {
|
|
156
|
+
trustClass: "community_untrusted_v1";
|
|
157
|
+
status: "cryptographically_valid";
|
|
158
|
+
bundleId: string;
|
|
159
|
+
publisherKeyId: string;
|
|
160
|
+
activeRecordCount: number;
|
|
161
|
+
revokedRecordCount: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type PublicProjectionNotExportableReason =
|
|
165
|
+
| "private_route_identity"
|
|
166
|
+
| "unsupported_subject"
|
|
167
|
+
| "unsafe_public_field"
|
|
168
|
+
| "unsupported_adapter_family";
|
|
169
|
+
|
|
170
|
+
export type PublicEvidenceProjectionResult =
|
|
171
|
+
| { status: "exportable"; record: PublicEvidenceRecordV1 }
|
|
172
|
+
| { status: "not_exportable"; reason: PublicProjectionNotExportableReason; detailCode?: string };
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { EVIDENCE_LAYERS, VERDICTS, type EvidenceLayer } from "../constants";
|
|
2
|
+
import { isSha256Hex } from "../digest";
|
|
3
|
+
import { publicEvidenceId } from "./ids";
|
|
4
|
+
import { findPublicRouteRegistryEntry } from "./registry";
|
|
5
|
+
import {
|
|
6
|
+
PUBLIC_ADAPTER_FAMILIES,
|
|
7
|
+
PUBLIC_ROUTE_REGISTRY_SCHEMA_VERSION,
|
|
8
|
+
type PublicAdapterFamily,
|
|
9
|
+
type PublicAssertionSummaryV1,
|
|
10
|
+
type PublicEvidenceRecordV1,
|
|
11
|
+
type PublicEvidenceSubjectV1,
|
|
12
|
+
type PublicIncidentRefV1,
|
|
13
|
+
type PublicProtocolSubjectV1,
|
|
14
|
+
type PublicRouteRegistryEntryV1,
|
|
15
|
+
type PublicRouteRegistryManifestV1,
|
|
16
|
+
type PublicRouteSubjectV1,
|
|
17
|
+
type PublicTaskSubjectV1,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
const MAX_PUBLIC_STRING_BYTES = 4 * 1024;
|
|
21
|
+
const MAX_PUBLIC_ASSERTIONS = 64;
|
|
22
|
+
const MAX_PUBLIC_INCIDENT_REFS = 32;
|
|
23
|
+
const MAX_PUBLIC_ARTIFACT_REFS = 16;
|
|
24
|
+
const PUBLIC_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:+-]{0,255}$/;
|
|
25
|
+
const UTC_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
26
|
+
const SOURCE_COMMIT = /^[0-9a-f]{40}$/;
|
|
27
|
+
|
|
28
|
+
const PUBLIC_INCIDENT_CORPUS_IDS = new Set(
|
|
29
|
+
Array.from({ length: 21 }, (_, index) => `IC-${String(index + 1).padStart(3, "0")}`),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export class PublicEvidenceValidationError extends Error {
|
|
33
|
+
override readonly name = "PublicEvidenceValidationError";
|
|
34
|
+
|
|
35
|
+
constructor(readonly code: string, message: string) {
|
|
36
|
+
super(message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
41
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assertObject(value: unknown, field: string): Record<string, unknown> {
|
|
45
|
+
if (!isPlainObject(value)) {
|
|
46
|
+
throw new PublicEvidenceValidationError("invalid_type", `${field} must be an object`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertKnownKeys(
|
|
52
|
+
raw: Record<string, unknown>,
|
|
53
|
+
field: string,
|
|
54
|
+
allowed: readonly string[],
|
|
55
|
+
): void {
|
|
56
|
+
const allow = new Set(allowed);
|
|
57
|
+
for (const key of Object.keys(raw)) {
|
|
58
|
+
if (!allow.has(key)) {
|
|
59
|
+
throw new PublicEvidenceValidationError("unknown_field", `${field}.${key} is not public schema`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function assertString(value: unknown, field: string, max = MAX_PUBLIC_STRING_BYTES): string {
|
|
65
|
+
if (typeof value !== "string") {
|
|
66
|
+
throw new PublicEvidenceValidationError("invalid_type", `${field} must be a string`);
|
|
67
|
+
}
|
|
68
|
+
if (value.includes("\0")) {
|
|
69
|
+
throw new PublicEvidenceValidationError("unsafe_public_field", `${field} contains NUL`);
|
|
70
|
+
}
|
|
71
|
+
if (new TextEncoder().encode(value).byteLength > max) {
|
|
72
|
+
throw new PublicEvidenceValidationError("field_too_large", `${field} exceeds ${max} bytes`);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assertPublicIdentifier(value: unknown, field: string): string {
|
|
78
|
+
const result = assertString(value, field, 256);
|
|
79
|
+
if (!PUBLIC_IDENTIFIER.test(result)) {
|
|
80
|
+
throw new PublicEvidenceValidationError("unsafe_public_field", `${field} is not a closed public identifier`);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function assertBoolean(value: unknown, field: string): boolean {
|
|
86
|
+
if (value !== true && value !== false) {
|
|
87
|
+
throw new PublicEvidenceValidationError("invalid_type", `${field} must be boolean`);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function assertSha256(value: unknown, field: string): string {
|
|
93
|
+
const result = assertString(value, field, 64);
|
|
94
|
+
if (!isSha256Hex(result)) {
|
|
95
|
+
throw new PublicEvidenceValidationError("invalid_digest", `${field} must be lowercase sha256 hex`);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function assertClosed<T extends string>(
|
|
101
|
+
value: unknown,
|
|
102
|
+
field: string,
|
|
103
|
+
allowed: readonly T[],
|
|
104
|
+
): T {
|
|
105
|
+
if (typeof value !== "string" || !(allowed as readonly string[]).includes(value)) {
|
|
106
|
+
throw new PublicEvidenceValidationError("closed_set", `${field} is not in the public closed set`);
|
|
107
|
+
}
|
|
108
|
+
return value as T;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assertUtcDay(value: unknown, field: string): string {
|
|
112
|
+
const result = assertString(value, field, 10);
|
|
113
|
+
if (!UTC_DAY.test(result)) {
|
|
114
|
+
throw new PublicEvidenceValidationError("invalid_day", `${field} must be YYYY-MM-DD`);
|
|
115
|
+
}
|
|
116
|
+
const parsed = new Date(`${result}T00:00:00.000Z`);
|
|
117
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== result) {
|
|
118
|
+
throw new PublicEvidenceValidationError("invalid_day", `${field} must be a real UTC day`);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validateAdapterFamily(value: unknown, field: string): PublicAdapterFamily {
|
|
124
|
+
return assertClosed(value, field, PUBLIC_ADAPTER_FAMILIES);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function validateProtocolSubject(rawValue: unknown): PublicProtocolSubjectV1 {
|
|
128
|
+
const raw = assertObject(rawValue, "subject");
|
|
129
|
+
assertKnownKeys(raw, "subject", [
|
|
130
|
+
"subjectKind",
|
|
131
|
+
"compatibilityVersion",
|
|
132
|
+
"adapterFamily",
|
|
133
|
+
"inboundProtocol",
|
|
134
|
+
"upstreamProtocol",
|
|
135
|
+
"surface",
|
|
136
|
+
]);
|
|
137
|
+
if (raw.subjectKind !== "protocol") {
|
|
138
|
+
throw new PublicEvidenceValidationError("layer_subject_mismatch", "protocol layer requires protocol subject");
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
subjectKind: "protocol",
|
|
142
|
+
compatibilityVersion: assertPublicIdentifier(raw.compatibilityVersion, "subject.compatibilityVersion"),
|
|
143
|
+
adapterFamily: validateAdapterFamily(raw.adapterFamily, "subject.adapterFamily"),
|
|
144
|
+
inboundProtocol: assertPublicIdentifier(raw.inboundProtocol, "subject.inboundProtocol"),
|
|
145
|
+
upstreamProtocol: assertPublicIdentifier(raw.upstreamProtocol, "subject.upstreamProtocol"),
|
|
146
|
+
surface: assertPublicIdentifier(raw.surface, "subject.surface"),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function validateRouteSubject(rawValue: unknown): PublicRouteSubjectV1 {
|
|
151
|
+
const raw = assertObject(rawValue, "subject");
|
|
152
|
+
assertKnownKeys(raw, "subject", [
|
|
153
|
+
"subjectKind",
|
|
154
|
+
"providerId",
|
|
155
|
+
"modelId",
|
|
156
|
+
"adapterFamily",
|
|
157
|
+
"compatibilityVersion",
|
|
158
|
+
]);
|
|
159
|
+
if (raw.subjectKind !== "route") {
|
|
160
|
+
throw new PublicEvidenceValidationError("layer_subject_mismatch", "route layer requires route subject");
|
|
161
|
+
}
|
|
162
|
+
const providerId = assertPublicIdentifier(raw.providerId, "subject.providerId");
|
|
163
|
+
const modelId = assertPublicIdentifier(raw.modelId, "subject.modelId");
|
|
164
|
+
const adapterFamily = validateAdapterFamily(raw.adapterFamily, "subject.adapterFamily");
|
|
165
|
+
const entry = findPublicRouteRegistryEntry(providerId, modelId);
|
|
166
|
+
if (!entry || !entry.adapterFamilies.includes(adapterFamily)) {
|
|
167
|
+
throw new PublicEvidenceValidationError("public_registry_rejected", "route is not in the reviewed public registry");
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
subjectKind: "route",
|
|
171
|
+
providerId,
|
|
172
|
+
modelId,
|
|
173
|
+
adapterFamily,
|
|
174
|
+
compatibilityVersion: assertPublicIdentifier(raw.compatibilityVersion, "subject.compatibilityVersion"),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function validateTaskSubject(rawValue: unknown): PublicTaskSubjectV1 {
|
|
179
|
+
const raw = assertObject(rawValue, "subject");
|
|
180
|
+
assertKnownKeys(raw, "subject", [
|
|
181
|
+
"subjectKind",
|
|
182
|
+
"route",
|
|
183
|
+
"taskClassId",
|
|
184
|
+
"taskClassVersion",
|
|
185
|
+
"taskFixtureDigest",
|
|
186
|
+
"verifierManifestDigest",
|
|
187
|
+
"fabricCompatibilityVersion",
|
|
188
|
+
]);
|
|
189
|
+
if (raw.subjectKind !== "task") {
|
|
190
|
+
throw new PublicEvidenceValidationError("layer_subject_mismatch", "task layer requires task subject");
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
subjectKind: "task",
|
|
194
|
+
route: validateRouteSubject(raw.route),
|
|
195
|
+
taskClassId: assertPublicIdentifier(raw.taskClassId, "subject.taskClassId"),
|
|
196
|
+
taskClassVersion: assertPublicIdentifier(raw.taskClassVersion, "subject.taskClassVersion"),
|
|
197
|
+
taskFixtureDigest: assertSha256(raw.taskFixtureDigest, "subject.taskFixtureDigest"),
|
|
198
|
+
verifierManifestDigest: assertSha256(raw.verifierManifestDigest, "subject.verifierManifestDigest"),
|
|
199
|
+
fabricCompatibilityVersion: assertPublicIdentifier(
|
|
200
|
+
raw.fabricCompatibilityVersion,
|
|
201
|
+
"subject.fabricCompatibilityVersion",
|
|
202
|
+
),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function validateSubject(raw: unknown, layer: EvidenceLayer): PublicEvidenceSubjectV1 {
|
|
207
|
+
if (layer === "protocol_conformance") return validateProtocolSubject(raw);
|
|
208
|
+
if (layer === "live_route_compatibility") return validateRouteSubject(raw);
|
|
209
|
+
if (layer === "task_effectiveness") return validateTaskSubject(raw);
|
|
210
|
+
const _exhaustive: never = layer;
|
|
211
|
+
throw new PublicEvidenceValidationError("unsupported_layer", String(_exhaustive));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function validateAssertion(rawValue: unknown, index: number): PublicAssertionSummaryV1 {
|
|
215
|
+
const raw = assertObject(rawValue, `assertions[${index}]`);
|
|
216
|
+
assertKnownKeys(raw, `assertions[${index}]`, ["id", "required", "passed"]);
|
|
217
|
+
return {
|
|
218
|
+
id: assertPublicIdentifier(raw.id, `assertions[${index}].id`),
|
|
219
|
+
required: assertBoolean(raw.required, `assertions[${index}].required`),
|
|
220
|
+
passed: assertBoolean(raw.passed, `assertions[${index}].passed`),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function isPublicIncidentRef(value: unknown): value is string {
|
|
225
|
+
return typeof value === "string" && PUBLIC_INCIDENT_CORPUS_IDS.has(value);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function validateIncidentRef(rawValue: unknown, index: number): PublicIncidentRefV1 {
|
|
229
|
+
const raw = assertObject(rawValue, `incidentRefs[${index}]`);
|
|
230
|
+
assertKnownKeys(raw, `incidentRefs[${index}]`, ["corpusId"]);
|
|
231
|
+
const corpusId = assertString(raw.corpusId, `incidentRefs[${index}].corpusId`, 6);
|
|
232
|
+
if (!isPublicIncidentRef(corpusId)) {
|
|
233
|
+
throw new PublicEvidenceValidationError("incident_ref_rejected", `${corpusId} is not in the reviewed corpus`);
|
|
234
|
+
}
|
|
235
|
+
return { corpusId };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function validateUniqueIds(rawValue: unknown, field: string, max: number): string[] {
|
|
239
|
+
if (!Array.isArray(rawValue)) {
|
|
240
|
+
throw new PublicEvidenceValidationError("invalid_type", `${field} must be an array`);
|
|
241
|
+
}
|
|
242
|
+
if (rawValue.length > max) {
|
|
243
|
+
throw new PublicEvidenceValidationError("array_too_large", `${field} exceeds ${max}`);
|
|
244
|
+
}
|
|
245
|
+
const values = rawValue.map((value, index) => assertSha256(value, `${field}[${index}]`));
|
|
246
|
+
if (new Set(values).size !== values.length) {
|
|
247
|
+
throw new PublicEvidenceValidationError("duplicate_id", `${field} contains duplicates`);
|
|
248
|
+
}
|
|
249
|
+
return values;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function validatePublicEvidenceRecord(rawValue: unknown): PublicEvidenceRecordV1 {
|
|
253
|
+
const raw = assertObject(rawValue, "record");
|
|
254
|
+
assertKnownKeys(raw, "record", [
|
|
255
|
+
"recordId",
|
|
256
|
+
"subjectId",
|
|
257
|
+
"evidenceLayer",
|
|
258
|
+
"suiteId",
|
|
259
|
+
"suiteVersion",
|
|
260
|
+
"scenarioId",
|
|
261
|
+
"scenarioVersion",
|
|
262
|
+
"verdict",
|
|
263
|
+
"observedDayUtc",
|
|
264
|
+
"subject",
|
|
265
|
+
"assertions",
|
|
266
|
+
"incidentRefs",
|
|
267
|
+
"artifactRefs",
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
const evidenceLayer = assertClosed(raw.evidenceLayer, "record.evidenceLayer", EVIDENCE_LAYERS);
|
|
271
|
+
const subject = validateSubject(raw.subject, evidenceLayer);
|
|
272
|
+
const subjectId = assertSha256(raw.subjectId, "record.subjectId");
|
|
273
|
+
const expectedSubjectId = publicEvidenceId("subject", subject);
|
|
274
|
+
if (subjectId !== expectedSubjectId) {
|
|
275
|
+
throw new PublicEvidenceValidationError("subject_id_mismatch", "record.subjectId does not match public subject");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!Array.isArray(raw.assertions)) {
|
|
279
|
+
throw new PublicEvidenceValidationError("invalid_type", "record.assertions must be an array");
|
|
280
|
+
}
|
|
281
|
+
if (raw.assertions.length > MAX_PUBLIC_ASSERTIONS) {
|
|
282
|
+
throw new PublicEvidenceValidationError("array_too_large", `record.assertions exceeds ${MAX_PUBLIC_ASSERTIONS}`);
|
|
283
|
+
}
|
|
284
|
+
const assertions = raw.assertions.map(validateAssertion);
|
|
285
|
+
|
|
286
|
+
let incidentRefs: PublicIncidentRefV1[] | undefined;
|
|
287
|
+
if (raw.incidentRefs !== undefined) {
|
|
288
|
+
if (!Array.isArray(raw.incidentRefs)) {
|
|
289
|
+
throw new PublicEvidenceValidationError("invalid_type", "record.incidentRefs must be an array");
|
|
290
|
+
}
|
|
291
|
+
if (raw.incidentRefs.length > MAX_PUBLIC_INCIDENT_REFS) {
|
|
292
|
+
throw new PublicEvidenceValidationError(
|
|
293
|
+
"array_too_large",
|
|
294
|
+
`record.incidentRefs exceeds ${MAX_PUBLIC_INCIDENT_REFS}`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
incidentRefs = raw.incidentRefs.map(validateIncidentRef);
|
|
298
|
+
const ids = incidentRefs.map((ref) => ref.corpusId);
|
|
299
|
+
if (new Set(ids).size !== ids.length) {
|
|
300
|
+
throw new PublicEvidenceValidationError("duplicate_id", "record.incidentRefs contains duplicates");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const artifactRefs = raw.artifactRefs === undefined
|
|
305
|
+
? undefined
|
|
306
|
+
: validateUniqueIds(raw.artifactRefs, "record.artifactRefs", MAX_PUBLIC_ARTIFACT_REFS);
|
|
307
|
+
|
|
308
|
+
const withoutRecordId: Omit<PublicEvidenceRecordV1, "recordId"> = {
|
|
309
|
+
subjectId,
|
|
310
|
+
evidenceLayer,
|
|
311
|
+
suiteId: assertPublicIdentifier(raw.suiteId, "record.suiteId"),
|
|
312
|
+
suiteVersion: assertPublicIdentifier(raw.suiteVersion, "record.suiteVersion"),
|
|
313
|
+
scenarioId: assertPublicIdentifier(raw.scenarioId, "record.scenarioId"),
|
|
314
|
+
scenarioVersion: assertPublicIdentifier(raw.scenarioVersion, "record.scenarioVersion"),
|
|
315
|
+
verdict: assertClosed(raw.verdict, "record.verdict", VERDICTS),
|
|
316
|
+
observedDayUtc: assertUtcDay(raw.observedDayUtc, "record.observedDayUtc"),
|
|
317
|
+
subject,
|
|
318
|
+
assertions,
|
|
319
|
+
...(incidentRefs !== undefined ? { incidentRefs } : {}),
|
|
320
|
+
...(artifactRefs !== undefined ? { artifactRefs } : {}),
|
|
321
|
+
};
|
|
322
|
+
const recordId = assertSha256(raw.recordId, "record.recordId");
|
|
323
|
+
const expectedRecordId = publicEvidenceId("record", withoutRecordId);
|
|
324
|
+
if (recordId !== expectedRecordId) {
|
|
325
|
+
throw new PublicEvidenceValidationError("record_id_mismatch", "record.recordId does not match public record");
|
|
326
|
+
}
|
|
327
|
+
return { recordId, ...withoutRecordId };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function validateRegistryEntry(rawValue: unknown, index: number): PublicRouteRegistryEntryV1 {
|
|
331
|
+
const raw = assertObject(rawValue, `entries[${index}]`);
|
|
332
|
+
assertKnownKeys(raw, `entries[${index}]`, ["providerId", "modelId", "adapterFamilies"]);
|
|
333
|
+
if (!Array.isArray(raw.adapterFamilies) || raw.adapterFamilies.length === 0) {
|
|
334
|
+
throw new PublicEvidenceValidationError("invalid_registry", `entries[${index}].adapterFamilies must be non-empty`);
|
|
335
|
+
}
|
|
336
|
+
const adapterFamilies = raw.adapterFamilies.map((value, adapterIndex) =>
|
|
337
|
+
validateAdapterFamily(value, `entries[${index}].adapterFamilies[${adapterIndex}]`)
|
|
338
|
+
);
|
|
339
|
+
if (new Set(adapterFamilies).size !== adapterFamilies.length) {
|
|
340
|
+
throw new PublicEvidenceValidationError("duplicate_id", `entries[${index}].adapterFamilies contains duplicates`);
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
providerId: assertPublicIdentifier(raw.providerId, `entries[${index}].providerId`),
|
|
344
|
+
modelId: assertPublicIdentifier(raw.modelId, `entries[${index}].modelId`),
|
|
345
|
+
adapterFamilies,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function validatePublicRouteRegistryManifest(rawValue: unknown): PublicRouteRegistryManifestV1 {
|
|
350
|
+
const raw = assertObject(rawValue, "publicRouteRegistry");
|
|
351
|
+
assertKnownKeys(raw, "publicRouteRegistry", [
|
|
352
|
+
"schemaVersion",
|
|
353
|
+
"registryVersion",
|
|
354
|
+
"sourceCommit",
|
|
355
|
+
"entries",
|
|
356
|
+
"manifestDigest",
|
|
357
|
+
]);
|
|
358
|
+
if (raw.schemaVersion !== PUBLIC_ROUTE_REGISTRY_SCHEMA_VERSION) {
|
|
359
|
+
throw new PublicEvidenceValidationError("unsupported_version", "unsupported public route registry schema");
|
|
360
|
+
}
|
|
361
|
+
const registryVersion = assertPublicIdentifier(raw.registryVersion, "publicRouteRegistry.registryVersion");
|
|
362
|
+
const sourceCommit = assertString(raw.sourceCommit, "publicRouteRegistry.sourceCommit", 40);
|
|
363
|
+
if (!SOURCE_COMMIT.test(sourceCommit)) {
|
|
364
|
+
throw new PublicEvidenceValidationError("invalid_registry", "publicRouteRegistry.sourceCommit must be a commit SHA");
|
|
365
|
+
}
|
|
366
|
+
if (!Array.isArray(raw.entries) || raw.entries.length === 0 || raw.entries.length > 512) {
|
|
367
|
+
throw new PublicEvidenceValidationError("invalid_registry", "publicRouteRegistry.entries must contain 1..512 entries");
|
|
368
|
+
}
|
|
369
|
+
const entries = raw.entries.map(validateRegistryEntry);
|
|
370
|
+
const identities = entries.map((entry) => `${entry.providerId}\0${entry.modelId}`);
|
|
371
|
+
if (new Set(identities).size !== identities.length) {
|
|
372
|
+
throw new PublicEvidenceValidationError("duplicate_id", "publicRouteRegistry.entries contains duplicates");
|
|
373
|
+
}
|
|
374
|
+
const manifestDigest = assertSha256(raw.manifestDigest, "publicRouteRegistry.manifestDigest");
|
|
375
|
+
const expectedDigest = publicEvidenceId("route_registry", {
|
|
376
|
+
schemaVersion: PUBLIC_ROUTE_REGISTRY_SCHEMA_VERSION,
|
|
377
|
+
registryVersion,
|
|
378
|
+
sourceCommit,
|
|
379
|
+
entries,
|
|
380
|
+
});
|
|
381
|
+
if (manifestDigest !== expectedDigest) {
|
|
382
|
+
throw new PublicEvidenceValidationError("digest_invalid", "publicRouteRegistry.manifestDigest mismatch");
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
schemaVersion: PUBLIC_ROUTE_REGISTRY_SCHEMA_VERSION,
|
|
386
|
+
registryVersion,
|
|
387
|
+
sourceCommit,
|
|
388
|
+
entries,
|
|
389
|
+
manifestDigest,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract for the dashboard-driven Codex app-server restart (#1046 follow-up).
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `system-restart-contract.ts`: that one restarts THIS proxy process
|
|
5
|
+
* and needs a pid-bound capability because it kills its own listener. This one asks
|
|
6
|
+
* matching Codex app-server children to exit so Codex rereads the catalog on next
|
|
7
|
+
* launch. It never touches the proxy and never spawns a replacement — whoever owns
|
|
8
|
+
* the app-server (the Codex app, an SSH bootstrap) relaunches it on next use.
|
|
9
|
+
*
|
|
10
|
+
* Scalar-only payload. A command line can contain a home directory and a username,
|
|
11
|
+
* and an OS error message often embeds a path, so neither crosses this boundary.
|
|
12
|
+
*
|
|
13
|
+
* Design and audit history: `devlog/_plan/260815_gui_codex_restart/`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const CODEX_RESTART_METHOD = "POST";
|
|
17
|
+
export const CODEX_RESTART_PATH = "/api/system/codex-restart";
|
|
18
|
+
export const CODEX_APP_SERVER_STATE_PATH = "/api/system/codex-app-server";
|
|
19
|
+
|
|
20
|
+
/** Mirrors CodexAppServerCatalogState so the GUI never imports runtime code. */
|
|
21
|
+
export type CodexAppServerState = "fresh" | "stale" | "not_running" | "unknown";
|
|
22
|
+
|
|
23
|
+
export type CodexRestartCode =
|
|
24
|
+
| "stopped"
|
|
25
|
+
| "nothing_running"
|
|
26
|
+
| "enumeration_unavailable"
|
|
27
|
+
| "partially_stopped";
|
|
28
|
+
|
|
29
|
+
/** GET response: a cheap reading with no side effects. It never signals. */
|
|
30
|
+
export interface CodexAppServerStateResponse {
|
|
31
|
+
state: CodexAppServerState;
|
|
32
|
+
runningCount: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** POST response. All four arrays are pid lists — never command lines. */
|
|
36
|
+
export interface CodexRestartResponse {
|
|
37
|
+
success: boolean;
|
|
38
|
+
/** Classifier reading taken BEFORE any signal, so the UI can explain why it acted. */
|
|
39
|
+
stateBefore: CodexAppServerState;
|
|
40
|
+
/** Whether a catalog or cache write happened during this request. */
|
|
41
|
+
synced: boolean;
|
|
42
|
+
requested: number[];
|
|
43
|
+
stopped: number[];
|
|
44
|
+
surviving: number[];
|
|
45
|
+
failed: number[];
|
|
46
|
+
code: CodexRestartCode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const APP_SERVER_STATES: readonly string[] = ["fresh", "stale", "not_running", "unknown"];
|
|
50
|
+
const RESTART_CODES: readonly string[] = [
|
|
51
|
+
"stopped",
|
|
52
|
+
"nothing_running",
|
|
53
|
+
"enumeration_unavailable",
|
|
54
|
+
"partially_stopped",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A pid is a positive safe integer. Accepting a float or a negative number would
|
|
59
|
+
* let a malformed body reach UI code that renders counts and indexes lengths.
|
|
60
|
+
*/
|
|
61
|
+
function isPidList(value: unknown): value is number[] {
|
|
62
|
+
return Array.isArray(value)
|
|
63
|
+
&& value.every(entry => typeof entry === "number" && Number.isSafeInteger(entry) && entry > 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Runtime guard for GUI consumers: a 2xx body is not automatically this shape.
|
|
68
|
+
*
|
|
69
|
+
* Structure is not enough. A body can be structurally valid and still contradict
|
|
70
|
+
* itself — `code: "stopped"` alongside surviving pids, or `success: true` with a
|
|
71
|
+
* failure code — and a caller that trusts it reports a success that did not happen.
|
|
72
|
+
* A version-skewed or regressed proxy is exactly how that arrives, so the
|
|
73
|
+
* cross-field invariants are checked here rather than assumed.
|
|
74
|
+
*/
|
|
75
|
+
export function isCodexRestartResponse(value: unknown): value is CodexRestartResponse {
|
|
76
|
+
if (typeof value !== "object" || value === null) return false;
|
|
77
|
+
const view = value as Record<string, unknown>;
|
|
78
|
+
const structural = typeof view.success === "boolean"
|
|
79
|
+
&& typeof view.synced === "boolean"
|
|
80
|
+
&& typeof view.stateBefore === "string"
|
|
81
|
+
&& APP_SERVER_STATES.includes(view.stateBefore)
|
|
82
|
+
&& typeof view.code === "string"
|
|
83
|
+
&& RESTART_CODES.includes(view.code)
|
|
84
|
+
&& isPidList(view.requested)
|
|
85
|
+
&& isPidList(view.stopped)
|
|
86
|
+
&& isPidList(view.surviving)
|
|
87
|
+
&& isPidList(view.failed);
|
|
88
|
+
if (!structural) return false;
|
|
89
|
+
|
|
90
|
+
const success = view.success as boolean;
|
|
91
|
+
const code = view.code as CodexRestartCode;
|
|
92
|
+
const surviving = view.surviving as number[];
|
|
93
|
+
const failed = view.failed as number[];
|
|
94
|
+
const stopped = view.stopped as number[];
|
|
95
|
+
|
|
96
|
+
// `success` and `code` must agree: only partially_stopped is an unsuccessful code.
|
|
97
|
+
if (success !== (code !== "partially_stopped")) return false;
|
|
98
|
+
// A clean outcome cannot leave anything behind.
|
|
99
|
+
if (success && (surviving.length > 0 || failed.length > 0)) return false;
|
|
100
|
+
// An unsuccessful outcome must name what survived.
|
|
101
|
+
if (!success && surviving.length === 0 && failed.length === 0) return false;
|
|
102
|
+
// Nothing can be reported stopped when the service says nothing was running.
|
|
103
|
+
if ((code === "nothing_running" || code === "enumeration_unavailable") && stopped.length > 0) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isCodexAppServerStateResponse(
|
|
110
|
+
value: unknown,
|
|
111
|
+
): value is CodexAppServerStateResponse {
|
|
112
|
+
if (typeof value !== "object" || value === null) return false;
|
|
113
|
+
const view = value as Record<string, unknown>;
|
|
114
|
+
return typeof view.state === "string"
|
|
115
|
+
&& APP_SERVER_STATES.includes(view.state)
|
|
116
|
+
&& typeof view.runningCount === "number"
|
|
117
|
+
&& Number.isSafeInteger(view.runningCount)
|
|
118
|
+
&& view.runningCount >= 0;
|
|
119
|
+
}
|
|
120
|
+
|