@bitkyc08/opencodex 2.19.0 → 2.21.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.
Files changed (130) hide show
  1. package/gui/dist/assets/index-BOFeam5a.js +76 -0
  2. package/gui/dist/assets/index-Xq49CY8F.css +1 -0
  3. package/gui/dist/index.html +2 -2
  4. package/package.json +1 -1
  5. package/src/adapters/anthropic.ts +2 -28
  6. package/src/adapters/google.ts +31 -3
  7. package/src/adapters/mimo-free.ts +17 -0
  8. package/src/adapters/openai-chat.ts +392 -40
  9. package/src/adapters/registry.ts +144 -0
  10. package/src/adapters/responses-tool-schema.ts +67 -0
  11. package/src/bridge.ts +15 -2
  12. package/src/chat/inbound.ts +13 -7
  13. package/src/claude/gateway-cache.ts +41 -4
  14. package/src/cli/claude.ts +3 -2
  15. package/src/cli/combo.ts +3 -0
  16. package/src/cli/dispatch.ts +8 -0
  17. package/src/cli/export-command.ts +2 -2
  18. package/src/cli/help.ts +2 -0
  19. package/src/cli/index.ts +3 -2
  20. package/src/cli/lab.ts +135 -1
  21. package/src/cli/minimax.ts +491 -0
  22. package/src/cli/models-runtime.ts +22 -1
  23. package/src/cli/models.ts +67 -2
  24. package/src/cli/opencode.ts +2 -1
  25. package/src/cli/registry.ts +22 -2
  26. package/src/clients/config-export.ts +125 -7
  27. package/src/codex/app-server-processes.ts +57 -2
  28. package/src/codex/app-server-restart-service.ts +232 -0
  29. package/src/codex/catalog/aggregation.ts +10 -1
  30. package/src/codex/catalog/effort.ts +15 -3
  31. package/src/codex/catalog/parsing.ts +3 -1
  32. package/src/codex/catalog/provider-fetch.ts +45 -5
  33. package/src/codex/catalog/sync.ts +74 -4
  34. package/src/codex/convergence.ts +2 -0
  35. package/src/combos/index.ts +1 -0
  36. package/src/combos/request.ts +30 -0
  37. package/src/combos/types.ts +6 -0
  38. package/src/config.ts +52 -0
  39. package/src/generated/compatibility-version.json +241 -85
  40. package/src/images/loop.ts +26 -6
  41. package/src/integrations/registry.ts +7 -0
  42. package/src/lab/conformance/jcs.ts +42 -2
  43. package/src/lab/conformance/negative-controls.ts +6 -2
  44. package/src/lab/conformance/runner.ts +16 -5
  45. package/src/lab/fabric/observe.ts +49 -14
  46. package/src/lab/index.ts +16 -0
  47. package/src/lab/ledger/purge.ts +152 -83
  48. package/src/lab/ledger/store.ts +168 -54
  49. package/src/lab/observe/from-conformance.ts +8 -6
  50. package/src/lab/observe/from-live.ts +8 -2
  51. package/src/lab/paths.ts +23 -0
  52. package/src/lab/public/bundle.ts +217 -0
  53. package/src/lab/public/community-authority.ts +175 -0
  54. package/src/lab/public/community-files.ts +29 -0
  55. package/src/lab/public/community.ts +479 -0
  56. package/src/lab/public/file-safety.ts +155 -0
  57. package/src/lab/public/ids.ts +26 -0
  58. package/src/lab/public/index.ts +16 -0
  59. package/src/lab/public/mutation-lock.ts +424 -0
  60. package/src/lab/public/operator.ts +353 -0
  61. package/src/lab/public/origin-purge.ts +79 -0
  62. package/src/lab/public/origin.ts +203 -0
  63. package/src/lab/public/privacy.ts +143 -0
  64. package/src/lab/public/private-file.ts +261 -0
  65. package/src/lab/public/project.ts +124 -0
  66. package/src/lab/public/purge-test-fault.ts +21 -0
  67. package/src/lab/public/purge.ts +223 -0
  68. package/src/lab/public/registry.ts +44 -0
  69. package/src/lab/public/revocation.ts +252 -0
  70. package/src/lab/public/signature.ts +219 -0
  71. package/src/lab/public/storage.ts +105 -0
  72. package/src/lab/public/strict-json.ts +206 -0
  73. package/src/lab/public/time.ts +26 -0
  74. package/src/lab/public/types.ts +172 -0
  75. package/src/lab/public/validate.ts +391 -0
  76. package/src/lib/codex-restart-contract.ts +120 -0
  77. package/src/lib/lab-activation.ts +109 -47
  78. package/src/lib/lab-live-pinned-sender.ts +16 -5
  79. package/src/lib/pinned-http.ts +70 -16
  80. package/src/lib/self-launch-argv.ts +15 -0
  81. package/src/lib/state-store-registrations.ts +2 -0
  82. package/src/lib/upstream-reachability.ts +4 -0
  83. package/src/lib/windows-elevation.ts +10 -1
  84. package/src/providers/derive.ts +24 -4
  85. package/src/providers/registry.ts +14 -4
  86. package/src/providers/request-pacing.ts +310 -0
  87. package/src/providers/service-tier.ts +143 -0
  88. package/src/providers/static-model-discovery.ts +86 -0
  89. package/src/reasoning-effort.ts +27 -1
  90. package/src/responses/custom-tool-compat.ts +4 -1
  91. package/src/responses/parser.ts +7 -1
  92. package/src/responses/provider-opaque-metadata.ts +73 -0
  93. package/src/responses/schema.ts +6 -0
  94. package/src/router.ts +23 -6
  95. package/src/routing/capability.ts +4 -2
  96. package/src/routing/compatibility/behavior.ts +5 -1
  97. package/src/server/adapter-resolve.ts +2 -32
  98. package/src/server/auth-cors.ts +50 -6
  99. package/src/server/chat-completions.ts +74 -36
  100. package/src/server/chat-native-sse.ts +331 -0
  101. package/src/server/chat-native.ts +371 -0
  102. package/src/server/management/combo-routes.ts +16 -2
  103. package/src/server/management/config-routes.ts +6 -4
  104. package/src/server/management/context.ts +17 -0
  105. package/src/server/management/lab-routes.ts +181 -19
  106. package/src/server/management/model-routes.ts +76 -2
  107. package/src/server/management/model-rows.ts +8 -0
  108. package/src/server/management/provider-capability-config.ts +48 -0
  109. package/src/server/management/provider-routes.ts +76 -4
  110. package/src/server/management/system-restart.ts +4 -2
  111. package/src/server/management/system-routes.ts +38 -0
  112. package/src/server/relay.ts +17 -3
  113. package/src/server/responses/compact.ts +4 -1
  114. package/src/server/responses/core.ts +257 -46
  115. package/src/server/responses/empty-completion-guard.ts +276 -0
  116. package/src/server/responses/fetch-helpers.ts +35 -4
  117. package/src/server/responses/pacing-overload.ts +13 -0
  118. package/src/server/responses/policy-fallback.ts +16 -2
  119. package/src/server/responses/terminal-guard.ts +1 -1
  120. package/src/server/responses/upstream-error.ts +5 -0
  121. package/src/server/responses.ts +17 -2
  122. package/src/server/system-env.ts +1 -1
  123. package/src/types.ts +84 -5
  124. package/src/update/index.ts +6 -5
  125. package/src/update/job.ts +5 -6
  126. package/src/update/notify.ts +5 -3
  127. package/src/usage/log.ts +11 -1
  128. package/src/web-search/loop.ts +21 -5
  129. package/gui/dist/assets/index-CQ7bIKee.css +0 -1
  130. package/gui/dist/assets/index-D_JUZLEC.js +0 -76
package/src/lab/paths.ts CHANGED
@@ -81,10 +81,25 @@ export function labScratchDir(configDir = getConfigDir()): string {
81
81
  return join(labRoot(configDir), "scratch");
82
82
  }
83
83
 
84
+ /** Shared Lab export directory. Public evidence bundles intentionally live here too. */
84
85
  export function labExportDir(configDir = getConfigDir()): string {
85
86
  return join(labRoot(configDir), "export");
86
87
  }
87
88
 
89
+ export function labCommunityDir(configDir = getConfigDir()): string {
90
+ return join(labRoot(configDir), "community");
91
+ }
92
+
93
+ export function labPublicOriginDir(configDir = getConfigDir()): string {
94
+ return join(labRoot(configDir), "public-origin-v1");
95
+ }
96
+
97
+ export const LAB_PUBLIC_PUBLISHER_KEY_FILE = "publisher-ed25519.pem";
98
+
99
+ export function labPublicPublisherKeyPath(configDir = getConfigDir()): string {
100
+ return join(labRoot(configDir), LAB_PUBLIC_PUBLISHER_KEY_FILE);
101
+ }
102
+
88
103
  /** Opaque per-installation salt for local fingerprinting (never exported as evidence). */
89
104
  export function labInstallationSaltPath(configDir = getConfigDir()): string {
90
105
  return join(labRoot(configDir), "installation-salt.bin");
@@ -110,15 +125,21 @@ export function ensureLabDirs(configDir = getConfigDir()): {
110
125
  artifactsDir: string;
111
126
  scratchDir: string;
112
127
  exportDir: string;
128
+ communityDir: string;
129
+ publicOriginDir: string;
113
130
  } {
114
131
  const root = labRoot(configDir);
115
132
  const artifactsDir = labArtifactsDir(configDir);
116
133
  const scratchDir = labScratchDir(configDir);
117
134
  const exportDir = labExportDir(configDir);
135
+ const communityDir = labCommunityDir(configDir);
136
+ const publicOriginDir = labPublicOriginDir(configDir);
118
137
  ensureRestrictedDir(root, root);
119
138
  ensureRestrictedDir(artifactsDir, root);
120
139
  ensureRestrictedDir(scratchDir, root);
121
140
  ensureRestrictedDir(exportDir, root);
141
+ ensureRestrictedDir(communityDir, root);
142
+ ensureRestrictedDir(publicOriginDir, root);
122
143
  return {
123
144
  root,
124
145
  ledgerPath: labLedgerPath(configDir),
@@ -126,5 +147,7 @@ export function ensureLabDirs(configDir = getConfigDir()): {
126
147
  artifactsDir,
127
148
  scratchDir,
128
149
  exportDir,
150
+ communityDir,
151
+ publicOriginDir,
129
152
  };
130
153
  }
@@ -0,0 +1,217 @@
1
+ import { jcsStringify } from "../digest";
2
+ import { publicEvidenceId } from "./ids";
3
+ import {
4
+ PUBLIC_EVIDENCE_BUNDLE_SCHEMA_VERSION,
5
+ PUBLIC_EXPORT_POLICY_VERSION,
6
+ type PublicArtifactV1,
7
+ type PublicEvidenceBundleUnsignedV1,
8
+ type PublicEvidenceRecordV1,
9
+ type PublicPublisherV1,
10
+ } from "./types";
11
+ import { PublicEvidenceValidationError, validatePublicEvidenceRecord } from "./validate";
12
+
13
+ export const MAX_PUBLIC_BUNDLE_BYTES = 2 * 1024 * 1024;
14
+ export const MAX_PUBLIC_BUNDLE_RECORDS = 256;
15
+ export const MAX_PUBLIC_BUNDLE_ARTIFACTS = 16;
16
+ export const MAX_PUBLIC_ARTIFACT_BYTES = 256 * 1024;
17
+ export const MAX_PUBLIC_ARTIFACT_BYTES_TOTAL = 1024 * 1024;
18
+
19
+ export interface PublicEvidenceContentInput {
20
+ records: PublicEvidenceRecordV1[];
21
+ artifacts: PublicArtifactV1[];
22
+ createdDayUtc: string;
23
+ }
24
+
25
+ export interface BuildPublicEvidenceBundleInput extends PublicEvidenceContentInput {
26
+ publisher: PublicPublisherV1;
27
+ }
28
+
29
+ /** Deterministic, locale-independent code-unit ordering for canonical identity. */
30
+ function compareCanonicalId(a: string, b: string): number {
31
+ if (a < b) return -1;
32
+ if (a > b) return 1;
33
+ return 0;
34
+ }
35
+
36
+ function utcDay(value: string): string {
37
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
38
+ throw new PublicEvidenceValidationError("invalid_day", "createdDayUtc must be YYYY-MM-DD");
39
+ }
40
+ const parsed = new Date(`${value}T00:00:00.000Z`);
41
+ if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {
42
+ throw new PublicEvidenceValidationError("invalid_day", "createdDayUtc must be a real UTC day");
43
+ }
44
+ return value;
45
+ }
46
+
47
+ function validatePublisher(publisher: PublicPublisherV1): PublicPublisherV1 {
48
+ const raw = publisher as unknown as Record<string, unknown>;
49
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
50
+ throw new PublicEvidenceValidationError("invalid_publisher", "publisher must be an object");
51
+ }
52
+ const keys = Object.keys(raw);
53
+ if (keys.some((key) => !["algorithm", "keyId", "publicKey"].includes(key))) {
54
+ throw new PublicEvidenceValidationError("unknown_field", "publisher contains unknown fields");
55
+ }
56
+ if (publisher.algorithm !== "ed25519") {
57
+ throw new PublicEvidenceValidationError("unsupported_algorithm", "publisher must use ed25519");
58
+ }
59
+ if (!/^[0-9a-f]{64}$/.test(publisher.keyId)) {
60
+ throw new PublicEvidenceValidationError("invalid_publisher", "publisher.keyId must be sha256 hex");
61
+ }
62
+ if (typeof publisher.publicKey !== "string" || publisher.publicKey.length === 0 || publisher.publicKey.length > 1024) {
63
+ throw new PublicEvidenceValidationError("invalid_publisher", "publisher.publicKey is invalid");
64
+ }
65
+ const publicKeyBytes = Buffer.from(publisher.publicKey, "base64");
66
+ if (publicKeyBytes.byteLength === 0 || publicKeyBytes.toString("base64") !== publisher.publicKey) {
67
+ throw new PublicEvidenceValidationError("invalid_publisher", "publisher.publicKey must use canonical base64");
68
+ }
69
+ const expectedKeyId = publicEvidenceId("publisher_key", {
70
+ algorithm: publisher.algorithm,
71
+ publicKey: publisher.publicKey,
72
+ });
73
+ if (publisher.keyId !== expectedKeyId) {
74
+ throw new PublicEvidenceValidationError("publisher_key_id_mismatch", "publisher.keyId does not match public key");
75
+ }
76
+ return { algorithm: "ed25519", keyId: publisher.keyId, publicKey: publisher.publicKey };
77
+ }
78
+
79
+ function validateArtifacts(artifacts: PublicArtifactV1[]): PublicArtifactV1[] {
80
+ if (!Array.isArray(artifacts) || artifacts.length > MAX_PUBLIC_BUNDLE_ARTIFACTS) {
81
+ throw new PublicEvidenceValidationError("array_too_large", `artifacts exceeds ${MAX_PUBLIC_BUNDLE_ARTIFACTS}`);
82
+ }
83
+ let aggregate = 0;
84
+ const ids = new Set<string>();
85
+ return artifacts.map((artifact, index) => {
86
+ const raw = artifact as unknown as Record<string, unknown>;
87
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
88
+ throw new PublicEvidenceValidationError("invalid_artifact", `artifacts[${index}] must be an object`);
89
+ }
90
+ if (Object.keys(raw).some((key) => !["artifactId", "artifactClass", "mediaType", "byteCount", "contentBase64"].includes(key))) {
91
+ throw new PublicEvidenceValidationError("unknown_field", `artifacts[${index}] contains unknown fields`);
92
+ }
93
+ if (!/^[0-9a-f]{64}$/.test(artifact.artifactId)) {
94
+ throw new PublicEvidenceValidationError("invalid_artifact", `artifacts[${index}].artifactId is invalid`);
95
+ }
96
+ if (typeof artifact.artifactClass !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:+-]{0,255}$/.test(artifact.artifactClass)) {
97
+ throw new PublicEvidenceValidationError("invalid_artifact", `artifacts[${index}].artifactClass is invalid`);
98
+ }
99
+ if (typeof artifact.mediaType !== "string" || artifact.mediaType.length === 0 || artifact.mediaType.length > 256) {
100
+ throw new PublicEvidenceValidationError("invalid_artifact", `artifacts[${index}].mediaType is invalid`);
101
+ }
102
+ if (!Number.isInteger(artifact.byteCount) || artifact.byteCount < 0 || artifact.byteCount > MAX_PUBLIC_ARTIFACT_BYTES) {
103
+ throw new PublicEvidenceValidationError("artifact_too_large", `artifacts[${index}].byteCount is invalid`);
104
+ }
105
+ let bytes: Buffer;
106
+ try {
107
+ bytes = Buffer.from(artifact.contentBase64, "base64");
108
+ } catch {
109
+ throw new PublicEvidenceValidationError("invalid_artifact", `artifacts[${index}].contentBase64 is invalid`);
110
+ }
111
+ if (bytes.byteLength !== artifact.byteCount || bytes.toString("base64") !== artifact.contentBase64) {
112
+ throw new PublicEvidenceValidationError("invalid_artifact", `artifacts[${index}] byte count or base64 is non-canonical`);
113
+ }
114
+ const expectedArtifactId = publicEvidenceId("artifact", {
115
+ artifactClass: artifact.artifactClass,
116
+ mediaType: artifact.mediaType,
117
+ byteCount: artifact.byteCount,
118
+ contentBase64: artifact.contentBase64,
119
+ });
120
+ if (artifact.artifactId !== expectedArtifactId) {
121
+ throw new PublicEvidenceValidationError("artifact_id_mismatch", `artifacts[${index}].artifactId mismatch`);
122
+ }
123
+ aggregate += artifact.byteCount;
124
+ if (aggregate > MAX_PUBLIC_ARTIFACT_BYTES_TOTAL) {
125
+ throw new PublicEvidenceValidationError("artifact_aggregate_too_large", "artifact aggregate exceeds 1 MiB");
126
+ }
127
+ if (ids.has(artifact.artifactId)) {
128
+ throw new PublicEvidenceValidationError("duplicate_id", "artifacts contains duplicate ids");
129
+ }
130
+ ids.add(artifact.artifactId);
131
+ return { ...artifact };
132
+ });
133
+ }
134
+
135
+ /** Validate all publisher-independent bundle content before any signing-key state is touched. */
136
+ export function normalizePublicEvidenceContent(input: PublicEvidenceContentInput): PublicEvidenceContentInput {
137
+ if (!Array.isArray(input.records) || input.records.length > MAX_PUBLIC_BUNDLE_RECORDS) {
138
+ throw new PublicEvidenceValidationError("array_too_large", `records exceeds ${MAX_PUBLIC_BUNDLE_RECORDS}`);
139
+ }
140
+ const records = input.records
141
+ .map(validatePublicEvidenceRecord)
142
+ .sort((a, b) => compareCanonicalId(a.recordId, b.recordId));
143
+ if (new Set(records.map((record) => record.recordId)).size !== records.length) {
144
+ throw new PublicEvidenceValidationError("duplicate_id", "records contains duplicate ids");
145
+ }
146
+ const artifacts = validateArtifacts(input.artifacts)
147
+ .sort((a, b) => compareCanonicalId(a.artifactId, b.artifactId));
148
+ const artifactIds = new Set(artifacts.map((artifact) => artifact.artifactId));
149
+ for (const record of records) {
150
+ for (const artifactId of record.artifactRefs ?? []) {
151
+ if (!artifactIds.has(artifactId)) {
152
+ throw new PublicEvidenceValidationError("artifact_ref_missing", `record ${record.recordId} references a missing public artifact`);
153
+ }
154
+ }
155
+ }
156
+ return { records, artifacts, createdDayUtc: utcDay(input.createdDayUtc) };
157
+ }
158
+
159
+ export function canonicalPublicEvidenceContent(
160
+ input: PublicEvidenceContentInput,
161
+ ): { canonical: boolean; normalized: PublicEvidenceContentInput } {
162
+ const normalized = normalizePublicEvidenceContent(input);
163
+ const canonical = input.records.length === normalized.records.length
164
+ && input.artifacts.length === normalized.artifacts.length
165
+ && input.records.every((record, index) => record.recordId === normalized.records[index]!.recordId)
166
+ && input.artifacts.every((artifact, index) => artifact.artifactId === normalized.artifacts[index]!.artifactId);
167
+ return { canonical, normalized };
168
+ }
169
+
170
+ export function hasCanonicalPublicEvidenceOrder(input: PublicEvidenceContentInput): boolean {
171
+ return canonicalPublicEvidenceContent(input).canonical;
172
+ }
173
+
174
+ function buildFromNormalizedContent(
175
+ normalized: PublicEvidenceContentInput,
176
+ publisherInput: PublicPublisherV1,
177
+ ): PublicEvidenceBundleUnsignedV1 {
178
+ const publisher = validatePublisher(publisherInput);
179
+ const content = {
180
+ schemaVersion: PUBLIC_EVIDENCE_BUNDLE_SCHEMA_VERSION,
181
+ exportPolicyVersion: PUBLIC_EXPORT_POLICY_VERSION,
182
+ createdDayUtc: normalized.createdDayUtc,
183
+ publisher,
184
+ records: normalized.records,
185
+ artifacts: normalized.artifacts,
186
+ };
187
+ const bundleId = publicEvidenceId("bundle", content);
188
+ const bundleDigest = publicEvidenceId("bundle_digest", { ...content, bundleId });
189
+ const bundle: PublicEvidenceBundleUnsignedV1 = { ...content, bundleId, bundleDigest };
190
+ if (new TextEncoder().encode(jcsStringify(bundle)).byteLength > MAX_PUBLIC_BUNDLE_BYTES) {
191
+ throw new PublicEvidenceValidationError("bundle_too_large", "public bundle exceeds 2 MiB");
192
+ }
193
+ return bundle;
194
+ }
195
+
196
+ export function buildPublicEvidenceBundle(input: BuildPublicEvidenceBundleInput): PublicEvidenceBundleUnsignedV1 {
197
+ return buildFromNormalizedContent(normalizePublicEvidenceContent(input), input.publisher);
198
+ }
199
+
200
+ export function expectedPublicBundleIdentityFromNormalized(
201
+ normalized: PublicEvidenceContentInput,
202
+ publisher: PublicPublisherV1,
203
+ ): { bundleId: string; bundleDigest: string } {
204
+ const rebuilt = buildFromNormalizedContent(normalized, publisher);
205
+ return { bundleId: rebuilt.bundleId, bundleDigest: rebuilt.bundleDigest };
206
+ }
207
+
208
+ export function expectedPublicBundleIdentity(bundle: PublicEvidenceBundleUnsignedV1): { bundleId: string; bundleDigest: string } {
209
+ return expectedPublicBundleIdentityFromNormalized(
210
+ normalizePublicEvidenceContent({
211
+ records: bundle.records,
212
+ artifacts: bundle.artifacts,
213
+ createdDayUtc: bundle.createdDayUtc,
214
+ }),
215
+ bundle.publisher,
216
+ );
217
+ }
@@ -0,0 +1,175 @@
1
+ import { loadCaseAuthority } from "../conformance/manifest";
2
+ import {
3
+ FABRIC_COMPATIBILITY_VERSION,
4
+ FABRIC_SCENARIO_ID,
5
+ FABRIC_SCENARIO_VERSION,
6
+ FABRIC_SUITE_ID,
7
+ FABRIC_SUITE_VERSION,
8
+ FABRIC_TASK_CLASS_ID,
9
+ FABRIC_TASK_CLASS_VERSION,
10
+ } from "../fabric/constants";
11
+ import { loadFabricCaseAuthority } from "../fabric/manifest";
12
+ import { verifierManifestDigest } from "../fabric/subject";
13
+ import { findPublicRouteRegistryEntry } from "./registry";
14
+ import type { PublicEvidenceBundleV1, PublicEvidenceRecordV1, PublicRouteSubjectV1 } from "./types";
15
+ import { PublicEvidenceValidationError } from "./validate";
16
+
17
+ type ProtocolCaseAuthority = ReturnType<typeof loadCaseAuthority>;
18
+
19
+ interface ProtocolAuthoritySnapshot {
20
+ scenarioVersion: string;
21
+ suiteVersion: string;
22
+ sourceCommit: string;
23
+ load: () => ProtocolCaseAuthority;
24
+ }
25
+
26
+ // Public records are historical evidence. Never replace an authority entry when a
27
+ // protocol version advances: retain the old loader and append a new snapshot.
28
+ const PROTOCOL_AUTHORITY_SNAPSHOTS: readonly ProtocolAuthoritySnapshot[] = Object.freeze([
29
+ Object.freeze({
30
+ scenarioVersion: "1.0.0",
31
+ suiteVersion: "1.0.0",
32
+ sourceCommit: "3ad5bb6bd3f76f6879d84b78ea39edd3e01ec296",
33
+ load: loadCaseAuthority,
34
+ }),
35
+ ]);
36
+
37
+ const cachedCaseAuthorities = new Map<string, ProtocolCaseAuthority>();
38
+ let cachedFabricCaseAuthority: ReturnType<typeof loadFabricCaseAuthority> | null = null;
39
+ let cachedVerifierManifestDigest: string | null = null;
40
+
41
+ function protocolAuthorityKey(snapshot: ProtocolAuthoritySnapshot): string {
42
+ return `${snapshot.suiteVersion}\0${snapshot.scenarioVersion}`;
43
+ }
44
+
45
+ function caseAuthorityFor(record: PublicEvidenceRecordV1): ProtocolCaseAuthority {
46
+ const snapshot = PROTOCOL_AUTHORITY_SNAPSHOTS.find((candidate) =>
47
+ candidate.scenarioVersion === record.scenarioVersion
48
+ && candidate.suiteVersion === record.suiteVersion
49
+ );
50
+ if (!snapshot) {
51
+ throw new PublicEvidenceValidationError(
52
+ "public_authority",
53
+ "scenario/suite authority version is not retained",
54
+ );
55
+ }
56
+
57
+ const key = protocolAuthorityKey(snapshot);
58
+ const cached = cachedCaseAuthorities.get(key);
59
+ if (cached) return cached;
60
+
61
+ const authority = snapshot.load();
62
+ if (
63
+ String(authority.manifestDefaults.version) !== snapshot.scenarioVersion
64
+ || String(authority.manifestDefaults.suiteVersion) !== snapshot.suiteVersion
65
+ || authority.sourceCommit !== snapshot.sourceCommit
66
+ ) {
67
+ throw new Error("public protocol authority snapshot drift");
68
+ }
69
+ cachedCaseAuthorities.set(key, authority);
70
+ return authority;
71
+ }
72
+
73
+ function fabricCaseAuthority(): ReturnType<typeof loadFabricCaseAuthority> {
74
+ cachedFabricCaseAuthority ??= loadFabricCaseAuthority();
75
+ return cachedFabricCaseAuthority;
76
+ }
77
+
78
+ function reviewedVerifierManifestDigest(): string {
79
+ cachedVerifierManifestDigest ??= verifierManifestDigest();
80
+ return cachedVerifierManifestDigest;
81
+ }
82
+
83
+ function validateRouteAuthority(subject: PublicRouteSubjectV1): void {
84
+ const entry = findPublicRouteRegistryEntry(subject.providerId, subject.modelId);
85
+ if (!entry || !entry.adapterFamilies.includes(subject.adapterFamily)) {
86
+ throw new PublicEvidenceValidationError("public_authority", "public route is not in reviewed registry authority");
87
+ }
88
+ }
89
+
90
+ function validateAssertionAuthority(
91
+ record: PublicEvidenceRecordV1,
92
+ assertions: readonly { id: string; required: boolean }[],
93
+ ): void {
94
+ const allowed = new Map(assertions.map((assertion) => [assertion.id, assertion.required] as const));
95
+ if (allowed.size !== assertions.length) {
96
+ throw new PublicEvidenceValidationError("public_authority", "reviewed scenario assertion authority contains duplicates");
97
+ }
98
+ if (record.assertions.length !== allowed.size) {
99
+ throw new PublicEvidenceValidationError(
100
+ "public_authority",
101
+ "public assertion set does not exactly match reviewed scenario authority",
102
+ );
103
+ }
104
+ const seen = new Set<string>();
105
+ for (const assertion of record.assertions) {
106
+ if (seen.has(assertion.id)) {
107
+ throw new PublicEvidenceValidationError("public_authority", "public assertion set contains duplicate assertion ids");
108
+ }
109
+ seen.add(assertion.id);
110
+ if (!allowed.has(assertion.id) || allowed.get(assertion.id) !== assertion.required) {
111
+ throw new PublicEvidenceValidationError(
112
+ "public_authority",
113
+ "public assertion id/required flag is not in reviewed scenario authority",
114
+ );
115
+ }
116
+ }
117
+ for (const assertionId of allowed.keys()) {
118
+ if (!seen.has(assertionId)) {
119
+ throw new PublicEvidenceValidationError("public_authority", "public assertion set is missing reviewed scenario authority");
120
+ }
121
+ }
122
+ }
123
+
124
+ function validateTaskAuthority(record: PublicEvidenceRecordV1): void {
125
+ const fabricAuthority = fabricCaseAuthority();
126
+ const caseRecord = fabricAuthority.cases.find((candidate) => candidate.id === FABRIC_SCENARIO_ID);
127
+ if (
128
+ !caseRecord
129
+ || record.suiteId !== FABRIC_SUITE_ID
130
+ || record.suiteVersion !== FABRIC_SUITE_VERSION
131
+ || record.scenarioId !== FABRIC_SCENARIO_ID
132
+ || record.scenarioVersion !== FABRIC_SCENARIO_VERSION
133
+ || record.subject.subjectKind !== "task"
134
+ || record.subject.taskClassId !== FABRIC_TASK_CLASS_ID
135
+ || record.subject.taskClassVersion !== FABRIC_TASK_CLASS_VERSION
136
+ || record.subject.taskFixtureDigest !== caseRecord.fixture.digest
137
+ || record.subject.verifierManifestDigest !== reviewedVerifierManifestDigest()
138
+ || record.subject.fabricCompatibilityVersion !== FABRIC_COMPATIBILITY_VERSION
139
+ ) {
140
+ throw new PublicEvidenceValidationError("public_authority", "task scenario/verifier authority mismatch");
141
+ }
142
+ validateAssertionAuthority(record, caseRecord.assertions);
143
+ validateRouteAuthority(record.subject.route);
144
+ }
145
+
146
+ function validateScenarioAuthority(record: PublicEvidenceRecordV1): void {
147
+ if (record.evidenceLayer === "task_effectiveness") {
148
+ validateTaskAuthority(record);
149
+ return;
150
+ }
151
+
152
+ const authority = caseAuthorityFor(record);
153
+ const caseRecord = authority.cases.find((candidate) => candidate.id === record.scenarioId);
154
+ if (!caseRecord || caseRecord.suite !== record.suiteId) {
155
+ throw new PublicEvidenceValidationError("public_authority", "scenario/suite authority mismatch");
156
+ }
157
+ validateAssertionAuthority(record, caseRecord.assertions);
158
+
159
+ if (record.evidenceLayer === "live_route_compatibility") {
160
+ if (record.subject.subjectKind !== "route") {
161
+ throw new PublicEvidenceValidationError("public_authority", "live route subject mismatch");
162
+ }
163
+ validateRouteAuthority(record.subject);
164
+ }
165
+ }
166
+
167
+ /** Repository-owned authority gate used by both local signing and community imports. */
168
+ export function validatePublicEvidenceAuthorities(records: readonly PublicEvidenceRecordV1[]): void {
169
+ for (const record of records) validateScenarioAuthority(record);
170
+ }
171
+
172
+ export function validateCommunityEvidenceAuthorities(bundle: PublicEvidenceBundleV1): PublicEvidenceBundleV1 {
173
+ validatePublicEvidenceAuthorities(bundle.records);
174
+ return bundle;
175
+ }
@@ -0,0 +1,29 @@
1
+ const COMMUNITY_ID_FRAGMENT = "[0-9a-f]{64}";
2
+ const COMMUNITY_BUNDLE_FILE_RE = new RegExp(
3
+ `^bundle-(${COMMUNITY_ID_FRAGMENT})-(${COMMUNITY_ID_FRAGMENT})\\.json$`,
4
+ );
5
+ const COMMUNITY_REVOCATION_FILE_RE = new RegExp(
6
+ `^revocation-(${COMMUNITY_ID_FRAGMENT})\\.json$`,
7
+ );
8
+
9
+ export interface CommunityBundleFileIdentity {
10
+ publisherKeyId: string;
11
+ bundleId: string;
12
+ }
13
+
14
+ export function communityBundleFileName(publisherKeyId: string, bundleId: string): string {
15
+ return `bundle-${publisherKeyId}-${bundleId}.json`;
16
+ }
17
+
18
+ export function communityRevocationFileName(revocationId: string): string {
19
+ return `revocation-${revocationId}.json`;
20
+ }
21
+
22
+ export function parseCommunityBundleFileName(name: string): CommunityBundleFileIdentity | null {
23
+ const match = COMMUNITY_BUNDLE_FILE_RE.exec(name);
24
+ return match ? { publisherKeyId: match[1]!, bundleId: match[2]! } : null;
25
+ }
26
+
27
+ export function isCommunityRevocationFileName(name: string): boolean {
28
+ return COMMUNITY_REVOCATION_FILE_RE.test(name);
29
+ }