@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
@@ -0,0 +1,479 @@
1
+ import { lstatSync, readdirSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { jcsStringify } from "../digest";
4
+ import { ensureLabDirs, labCommunityDir } from "../paths";
5
+ import { validateCommunityEvidenceAuthorities } from "./community-authority";
6
+ import { communityBundleFileName } from "./community-files";
7
+ import { privateRegularFileSize, readPrivateRegularFile } from "./file-safety";
8
+ import { withPublicEvidenceMutationLock } from "./mutation-lock";
9
+ import { recordLocalPublicOrigin } from "./origin";
10
+ import {
11
+ cleanupStalePrivateFileStages,
12
+ cleanupStalePrivateFileStagesInDir,
13
+ isPrivateFileStageName,
14
+ publishPrivateFileExclusive,
15
+ } from "./private-file";
16
+ import { validatePublicEvidencePrivacy } from "./privacy";
17
+ import { verifyPublicEvidenceRevocation } from "./revocation";
18
+ import { loadExistingPublicPublisher, verifyPublicEvidenceBundle } from "./signature";
19
+ import { parseStrictPublicJson } from "./strict-json";
20
+ import type {
21
+ CommunityEvidenceSummaryV1,
22
+ PublicEvidenceBundleV1,
23
+ PublicEvidenceRevocationV1,
24
+ } from "./types";
25
+ import { PublicEvidenceValidationError } from "./validate";
26
+
27
+ const MAX_IMPORT_BYTES = 2 * 1024 * 1024;
28
+ const MAX_CACHE_FILES = 512;
29
+ const MAX_CACHE_BYTES = 64 * 1024 * 1024;
30
+ const MAX_DEPTH = 8;
31
+ const MAX_OBJECT_KEYS = 64;
32
+ const MAX_ARRAY_ELEMENTS = 512;
33
+ const MAX_GENERIC_STRING_BYTES = 384 * 1024;
34
+ const COMMUNITY_MUTATION_LOCK_NAME = ".mutation-lock";
35
+ const COMMUNITY_BUNDLE_FILE_RE = /^bundle-([0-9a-f]{64})-([0-9a-f]{64})\.json$/;
36
+ const COMMUNITY_REVOCATION_FILE_RE = /^revocation-([0-9a-f]{64})\.json$/;
37
+
38
+ const COMMUNITY_FILE_OPTIONS = {
39
+ maxBytes: MAX_IMPORT_BYTES,
40
+ errorCode: "community_unsafe_target",
41
+ errorMessage: "community object is not a bounded private regular file",
42
+ sizeErrorCode: "community_size",
43
+ sizeErrorMessage: "community file exceeds bound",
44
+ } as const;
45
+
46
+ type CommunitySummaryCache = {
47
+ directory: string;
48
+ fingerprint: string;
49
+ evidence: CommunityEvidenceSummaryV1[];
50
+ };
51
+
52
+ let communitySummaryCache: CommunitySummaryCache | null = null;
53
+
54
+ function assertId(value: string): string {
55
+ if (!/^[0-9a-f]{64}$/.test(value)) {
56
+ throw new PublicEvidenceValidationError("community_id", "community object id invalid");
57
+ }
58
+ return value;
59
+ }
60
+
61
+ function scanStructure(value: unknown, depth = 0): void {
62
+ if (depth > MAX_DEPTH) {
63
+ throw new PublicEvidenceValidationError("community_depth", "community JSON nesting depth exceeded");
64
+ }
65
+ if (typeof value === "string") {
66
+ if (new TextEncoder().encode(value).byteLength > MAX_GENERIC_STRING_BYTES || value.includes("\0")) {
67
+ throw new PublicEvidenceValidationError("community_string", "community string invalid or oversized");
68
+ }
69
+ return;
70
+ }
71
+ if (Array.isArray(value)) {
72
+ if (value.length > MAX_ARRAY_ELEMENTS) {
73
+ throw new PublicEvidenceValidationError("community_array", "community array bound exceeded");
74
+ }
75
+ for (const item of value) scanStructure(item, depth + 1);
76
+ return;
77
+ }
78
+ if (value && typeof value === "object") {
79
+ const keys = Object.keys(value);
80
+ if (keys.length > MAX_OBJECT_KEYS) {
81
+ throw new PublicEvidenceValidationError("community_object", "community object key bound exceeded");
82
+ }
83
+ for (const key of keys) {
84
+ if (new TextEncoder().encode(key).byteLength > 4096) {
85
+ throw new PublicEvidenceValidationError("community_key", "community key oversized");
86
+ }
87
+ scanStructure((value as Record<string, unknown>)[key], depth + 1);
88
+ }
89
+ }
90
+ }
91
+
92
+ function boundedInput(raw: unknown): unknown {
93
+ let bytes: Buffer;
94
+ if (raw instanceof Uint8Array) {
95
+ bytes = Buffer.from(raw);
96
+ } else if (typeof raw === "string") {
97
+ bytes = Buffer.from(raw, "utf8");
98
+ } else {
99
+ scanStructure(raw);
100
+ bytes = Buffer.from(jcsStringify(raw), "utf8");
101
+ }
102
+ if (bytes.byteLength > MAX_IMPORT_BYTES) {
103
+ throw new PublicEvidenceValidationError("community_size", "community import exceeds 2 MiB");
104
+ }
105
+ const parsed = parseStrictPublicJson(bytes, "community import");
106
+ scanStructure(parsed);
107
+ return parsed;
108
+ }
109
+
110
+ function assertCommunityArtifactAuthority(bundle: PublicEvidenceBundleV1): void {
111
+ if (bundle.artifacts.length !== 0) {
112
+ throw new PublicEvidenceValidationError(
113
+ "public_artifact_authority_required",
114
+ "community artifact bytes require reviewed public_export policy authority",
115
+ );
116
+ }
117
+ }
118
+
119
+ function verifiedBundle(raw: unknown): PublicEvidenceBundleV1 {
120
+ const result = verifyPublicEvidenceBundle(raw as PublicEvidenceBundleV1);
121
+ if (result.status !== "cryptographically_valid") {
122
+ throw new PublicEvidenceValidationError(result.status, "community bundle verification failed");
123
+ }
124
+ const bundle = validateCommunityEvidenceAuthorities(raw as PublicEvidenceBundleV1);
125
+ assertCommunityArtifactAuthority(bundle);
126
+ validatePublicEvidencePrivacy(bundle);
127
+ return bundle;
128
+ }
129
+
130
+ function bundleObjectPath(publisherKeyId: string, bundleId: string, configDir?: string): string {
131
+ return join(
132
+ labCommunityDir(configDir),
133
+ communityBundleFileName(assertId(publisherKeyId), assertId(bundleId)),
134
+ );
135
+ }
136
+
137
+ function revocationObjectPath(revocationId: string, configDir?: string): string {
138
+ return join(labCommunityDir(configDir), `revocation-${assertId(revocationId)}.json`);
139
+ }
140
+
141
+ function readBounded(path: string): Buffer {
142
+ cleanupStalePrivateFileStages(path);
143
+ return readPrivateRegularFile(path, COMMUNITY_FILE_OPTIONS);
144
+ }
145
+
146
+ function cacheUsage(configDir?: string): { names: string[]; bytes: number } {
147
+ ensureLabDirs(configDir);
148
+ const dir = labCommunityDir(configDir);
149
+ cleanupStalePrivateFileStagesInDir(dir);
150
+ const names = readdirSync(dir)
151
+ .filter((name) => name !== COMMUNITY_MUTATION_LOCK_NAME && !isPrivateFileStageName(name))
152
+ .sort();
153
+ if (names.length > MAX_CACHE_FILES) {
154
+ throw new PublicEvidenceValidationError("community_cache_bound", "community cache file bound exceeded");
155
+ }
156
+ let bytes = 0;
157
+ for (const name of names) {
158
+ bytes += privateRegularFileSize(join(dir, name), COMMUNITY_FILE_OPTIONS);
159
+ if (bytes > MAX_CACHE_BYTES) {
160
+ throw new PublicEvidenceValidationError("community_cache_bound", "community cache byte bound exceeded");
161
+ }
162
+ }
163
+ return { names, bytes };
164
+ }
165
+
166
+ function assertCacheCanAdd(byteCount: number, configDir?: string): void {
167
+ const usage = cacheUsage(configDir);
168
+ if (usage.names.length >= MAX_CACHE_FILES || usage.bytes + byteCount > MAX_CACHE_BYTES) {
169
+ throw new PublicEvidenceValidationError("community_cache_bound", "community cache capacity exceeded");
170
+ }
171
+ }
172
+
173
+ function persistAtLocked(
174
+ path: string,
175
+ kind: "bundle" | "revocation",
176
+ value: unknown,
177
+ configDir?: string,
178
+ onCommit?: () => void,
179
+ ): { path: string; created: boolean } {
180
+ const bytes = Buffer.from(jcsStringify(value), "utf8");
181
+ if (bytes.byteLength > MAX_IMPORT_BYTES) {
182
+ throw new PublicEvidenceValidationError("community_size", "community object exceeds bound");
183
+ }
184
+
185
+ let created = false;
186
+ try {
187
+ try {
188
+ const existing = readBounded(path);
189
+ if (!existing.equals(bytes)) {
190
+ throw new PublicEvidenceValidationError("community_conflict", `${kind} identity already exists with different bytes`);
191
+ }
192
+ } catch (error) {
193
+ if (error instanceof PublicEvidenceValidationError) throw error;
194
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
195
+ assertCacheCanAdd(bytes.byteLength, configDir);
196
+ const published = publishPrivateFileExclusive(path, bytes);
197
+ if (!published.created) {
198
+ const raced = readBounded(path);
199
+ if (!raced.equals(bytes)) {
200
+ throw new PublicEvidenceValidationError("community_conflict", `${kind} identity already exists with different bytes`);
201
+ }
202
+ } else {
203
+ created = true;
204
+ cacheUsage(configDir);
205
+ }
206
+ }
207
+
208
+ onCommit?.();
209
+ if (created) communitySummaryCache = null;
210
+ return { path, created };
211
+ } catch (error) {
212
+ if (created) {
213
+ try { unlinkSync(path); } catch { /* preserve commit error */ }
214
+ communitySummaryCache = null;
215
+ }
216
+ throw error;
217
+ }
218
+ }
219
+
220
+ function persistAt(
221
+ path: string,
222
+ kind: "bundle" | "revocation",
223
+ value: unknown,
224
+ configDir?: string,
225
+ onCommit?: () => void,
226
+ ): { path: string; created: boolean } {
227
+ return withPublicEvidenceMutationLock(
228
+ configDir,
229
+ () => persistAtLocked(path, kind, value, configDir, onCommit),
230
+ );
231
+ }
232
+
233
+ function readJson(path: string): unknown {
234
+ const parsed = parseStrictPublicJson(readBounded(path), "stored community object");
235
+ scanStructure(parsed);
236
+ return parsed;
237
+ }
238
+
239
+ function files(configDir?: string): string[] {
240
+ return cacheUsage(configDir).names;
241
+ }
242
+
243
+ function readVerifiedBundleAt(path: string): PublicEvidenceBundleV1 {
244
+ return verifiedBundle(readJson(path));
245
+ }
246
+
247
+ function bundleFromName(name: string, configDir?: string): PublicEvidenceBundleV1 | null {
248
+ const match = COMMUNITY_BUNDLE_FILE_RE.exec(name);
249
+ if (!match) return null;
250
+ const publisherKeyId = match[1]!;
251
+ const bundleId = match[2]!;
252
+ const bundle = readVerifiedBundleAt(bundleObjectPath(publisherKeyId, bundleId, configDir));
253
+ if (bundle.bundleId !== bundleId || bundle.publisher.keyId !== publisherKeyId) {
254
+ throw new PublicEvidenceValidationError("community_identity_mismatch", "stored community bundle does not match filename identity");
255
+ }
256
+ return bundle;
257
+ }
258
+
259
+ function bundlesFromNames(names: readonly string[], configDir?: string): PublicEvidenceBundleV1[] {
260
+ const bundles: PublicEvidenceBundleV1[] = [];
261
+ for (const name of names) {
262
+ const bundle = bundleFromName(name, configDir);
263
+ if (bundle) bundles.push(bundle);
264
+ }
265
+ return bundles;
266
+ }
267
+
268
+ function restoreOwnPublisherOrigin(bundle: PublicEvidenceBundleV1, configDir?: string): void {
269
+ const local = loadExistingPublicPublisher(configDir);
270
+ if (!local) return;
271
+ if (
272
+ local.publisher.algorithm !== bundle.publisher.algorithm
273
+ || local.publisher.keyId !== bundle.publisher.keyId
274
+ || local.publisher.publicKey !== bundle.publisher.publicKey
275
+ ) return;
276
+ recordLocalPublicOrigin({ publisherKeyId: bundle.publisher.keyId, bundleId: bundle.bundleId }, configDir);
277
+ }
278
+
279
+ export function importCommunityEvidenceBundle(
280
+ raw: unknown,
281
+ configDir?: string,
282
+ ): { created: boolean; status: "cryptographically_valid"; bundleId: string; publisherKeyId: string; path: string } {
283
+ const bundle = verifiedBundle(boundedInput(raw));
284
+ ensureLabDirs(configDir);
285
+ const stored = persistAt(
286
+ bundleObjectPath(bundle.publisher.keyId, bundle.bundleId, configDir),
287
+ "bundle",
288
+ bundle,
289
+ configDir,
290
+ () => restoreOwnPublisherOrigin(bundle, configDir),
291
+ );
292
+ return { ...stored, status: "cryptographically_valid", bundleId: bundle.bundleId, publisherKeyId: bundle.publisher.keyId };
293
+ }
294
+
295
+ function readCommunityEvidenceBundleForPublisherLocked(
296
+ bundleId: string,
297
+ publisherKeyId: string,
298
+ configDir?: string,
299
+ ): PublicEvidenceBundleV1 {
300
+ const bundle = readVerifiedBundleAt(bundleObjectPath(publisherKeyId, bundleId, configDir));
301
+ if (bundle.bundleId !== bundleId || bundle.publisher.keyId !== publisherKeyId) {
302
+ throw new PublicEvidenceValidationError("community_identity_mismatch", "stored community bundle does not match filename identity");
303
+ }
304
+ return bundle;
305
+ }
306
+
307
+ export function readCommunityEvidenceBundleForPublisher(
308
+ bundleId: string,
309
+ publisherKeyId: string,
310
+ configDir?: string,
311
+ ): PublicEvidenceBundleV1 {
312
+ return withPublicEvidenceMutationLock(
313
+ configDir,
314
+ () => readCommunityEvidenceBundleForPublisherLocked(bundleId, publisherKeyId, configDir),
315
+ );
316
+ }
317
+
318
+ type RevocationMetadata = {
319
+ publisher?: { keyId?: unknown };
320
+ targets?: Array<{ kind?: unknown; id?: unknown }>;
321
+ };
322
+
323
+ function resolveTargetBundle(
324
+ revocation: unknown,
325
+ bundles: readonly PublicEvidenceBundleV1[],
326
+ ): PublicEvidenceBundleV1 {
327
+ if (!revocation || typeof revocation !== "object") {
328
+ throw new PublicEvidenceValidationError("revocation_target", "revocation target metadata unavailable");
329
+ }
330
+ const raw = revocation as RevocationMetadata;
331
+ if (!Array.isArray(raw.targets) || typeof raw.publisher?.keyId !== "string") {
332
+ throw new PublicEvidenceValidationError("revocation_target", "revocation targets or publisher unavailable");
333
+ }
334
+ const publisherKeyId = assertId(raw.publisher.keyId);
335
+ const publisherBundles = bundles
336
+ .filter((bundle) => bundle.publisher.keyId === publisherKeyId)
337
+ .sort((a, b) => a.bundleId.localeCompare(b.bundleId));
338
+ const bundleTargets = raw.targets.filter((target) => target.kind === "bundle" && typeof target.id === "string");
339
+ if (bundleTargets.length > 0) {
340
+ const targetIds = new Set(bundleTargets.map((target) => target.id));
341
+ if (targetIds.size !== 1) {
342
+ throw new PublicEvidenceValidationError("revocation_target", "revocation bundle targets are ambiguous");
343
+ }
344
+ const id = [...targetIds][0]!;
345
+ const candidate = publisherBundles.find((bundle) => bundle.bundleId === id);
346
+ if (!candidate) throw new PublicEvidenceValidationError("revocation_target", "revocation target bundle not found");
347
+ return candidate;
348
+ }
349
+ const fullyMatching = publisherBundles.filter((bundle) => raw.targets!.every((target) =>
350
+ target.kind === "record" && typeof target.id === "string"
351
+ && bundle.records.some((record) => record.recordId === target.id),
352
+ ));
353
+ if (fullyMatching.length === 0) {
354
+ throw new PublicEvidenceValidationError(
355
+ "revocation_target",
356
+ "revocation targets do not resolve to a verified bundle for the same publisher",
357
+ );
358
+ }
359
+ return fullyMatching[0]!;
360
+ }
361
+
362
+ function findTargetBundleLocked(revocation: unknown, configDir?: string): PublicEvidenceBundleV1 {
363
+ const names = files(configDir);
364
+ const raw = revocation as RevocationMetadata;
365
+ const publisherKeyId = typeof raw?.publisher?.keyId === "string" ? assertId(raw.publisher.keyId) : null;
366
+ const directBundleIds = Array.isArray(raw?.targets)
367
+ ? [...new Set(raw.targets.filter((target) => target.kind === "bundle" && typeof target.id === "string").map((target) => target.id as string))]
368
+ : [];
369
+ if (publisherKeyId && directBundleIds.length === 1) {
370
+ try {
371
+ return readCommunityEvidenceBundleForPublisherLocked(
372
+ assertId(directBundleIds[0]!),
373
+ publisherKeyId,
374
+ configDir,
375
+ );
376
+ } catch (error) {
377
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
378
+ throw new PublicEvidenceValidationError(
379
+ "revocation_target",
380
+ "revocation target bundle not found",
381
+ );
382
+ }
383
+ throw error;
384
+ }
385
+ }
386
+ return resolveTargetBundle(revocation, bundlesFromNames(names, configDir));
387
+ }
388
+
389
+ export function importCommunityEvidenceRevocation(
390
+ raw: unknown,
391
+ configDir?: string,
392
+ ): { created: boolean; status: "cryptographically_valid"; revocationId: string; path: string } {
393
+ const parsed = boundedInput(raw);
394
+ ensureLabDirs(configDir);
395
+ return withPublicEvidenceMutationLock(configDir, () => {
396
+ const targetBundle = findTargetBundleLocked(parsed, configDir);
397
+ const verified = verifyPublicEvidenceRevocation(parsed, targetBundle);
398
+ if (verified.status !== "cryptographically_valid") {
399
+ throw new PublicEvidenceValidationError(verified.status, verified.detail ?? "community revocation verification failed");
400
+ }
401
+ const stored = persistAtLocked(
402
+ revocationObjectPath(verified.revocation.revocationId, configDir),
403
+ "revocation",
404
+ verified.revocation,
405
+ configDir,
406
+ );
407
+ return { ...stored, status: "cryptographically_valid", revocationId: verified.revocation.revocationId };
408
+ });
409
+ }
410
+
411
+ function communityFingerprint(names: readonly string[], configDir?: string): string {
412
+ const dir = labCommunityDir(configDir);
413
+ return names.map((name) => {
414
+ const stat = lstatSync(join(dir, name));
415
+ return [name, stat.dev, stat.ino, stat.mode, stat.nlink, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
416
+ }).join("\n");
417
+ }
418
+
419
+ function copySummaries(evidence: readonly CommunityEvidenceSummaryV1[]): CommunityEvidenceSummaryV1[] {
420
+ return evidence.map((row) => ({ ...row }));
421
+ }
422
+
423
+ function listCommunityEvidenceLocked(configDir?: string): CommunityEvidenceSummaryV1[] {
424
+ const names = files(configDir);
425
+ const directory = labCommunityDir(configDir);
426
+ const fingerprint = communityFingerprint(names, configDir);
427
+ if (communitySummaryCache?.directory === directory && communitySummaryCache.fingerprint === fingerprint) {
428
+ return copySummaries(communitySummaryCache.evidence);
429
+ }
430
+
431
+ const bundles = bundlesFromNames(names, configDir);
432
+ const revocations: PublicEvidenceRevocationV1[] = [];
433
+
434
+ for (const name of names) {
435
+ if (!COMMUNITY_REVOCATION_FILE_RE.test(name)) continue;
436
+ const raw = readJson(join(directory, name));
437
+ let targetBundle: PublicEvidenceBundleV1;
438
+ try {
439
+ targetBundle = resolveTargetBundle(raw, bundles);
440
+ } catch (error) {
441
+ if (error instanceof PublicEvidenceValidationError) continue;
442
+ throw error;
443
+ }
444
+ const verified = verifyPublicEvidenceRevocation(raw, targetBundle);
445
+ if (verified.status === "cryptographically_valid") revocations.push(verified.revocation);
446
+ }
447
+
448
+ const evidence = bundles.map((bundle) => {
449
+ const revoked = new Set<string>();
450
+ const bundleRecordIds = new Set(bundle.records.map((record) => record.recordId));
451
+ for (const revocation of revocations) {
452
+ if (revocation.publisher.keyId !== bundle.publisher.keyId
453
+ || revocation.publisher.publicKey !== bundle.publisher.publicKey) {
454
+ continue;
455
+ }
456
+ if (revocation.targets.some((target) => target.kind === "bundle" && target.id === bundle.bundleId)) {
457
+ for (const record of bundle.records) revoked.add(record.recordId);
458
+ }
459
+ for (const target of revocation.targets) {
460
+ if (target.kind === "record" && bundleRecordIds.has(target.id)) revoked.add(target.id);
461
+ }
462
+ }
463
+ return {
464
+ trustClass: "community_untrusted_v1" as const,
465
+ status: "cryptographically_valid" as const,
466
+ bundleId: bundle.bundleId,
467
+ publisherKeyId: bundle.publisher.keyId,
468
+ activeRecordCount: bundle.records.filter((record) => !revoked.has(record.recordId)).length,
469
+ revokedRecordCount: bundle.records.filter((record) => revoked.has(record.recordId)).length,
470
+ };
471
+ }).sort((a, b) => a.bundleId.localeCompare(b.bundleId) || a.publisherKeyId.localeCompare(b.publisherKeyId));
472
+
473
+ communitySummaryCache = { directory, fingerprint, evidence: copySummaries(evidence) };
474
+ return copySummaries(evidence);
475
+ }
476
+
477
+ export function listCommunityEvidence(configDir?: string): CommunityEvidenceSummaryV1[] {
478
+ return withPublicEvidenceMutationLock(configDir, () => listCommunityEvidenceLocked(configDir));
479
+ }
@@ -0,0 +1,155 @@
1
+ import {
2
+ closeSync,
3
+ constants as fsConstants,
4
+ fstatSync,
5
+ fsyncSync,
6
+ lstatSync,
7
+ openSync,
8
+ readFileSync,
9
+ readdirSync,
10
+ unlinkSync,
11
+ } from "node:fs";
12
+ import { basename, dirname, join } from "node:path";
13
+ import { isPrivateFileStageName } from "./private-file";
14
+ import { PublicEvidenceValidationError } from "./validate";
15
+
16
+ const O_NOFOLLOW = (fsConstants as { O_NOFOLLOW?: number }).O_NOFOLLOW ?? 0;
17
+
18
+ export interface PrivateRegularFileReadOptions {
19
+ maxBytes: number;
20
+ errorCode: string;
21
+ errorMessage: string;
22
+ sizeErrorCode?: string;
23
+ sizeErrorMessage?: string;
24
+ requireMode600?: boolean;
25
+ }
26
+
27
+ function sizeError(options: PrivateRegularFileReadOptions): PublicEvidenceValidationError {
28
+ return new PublicEvidenceValidationError(
29
+ options.sizeErrorCode ?? options.errorCode,
30
+ options.sizeErrorMessage ?? options.errorMessage,
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Heal only the publication-specific hard link left behind when the final name was linked
36
+ * but the parent-directory durability check failed. The stage must be target-scoped and
37
+ * inode-identical to the final file; unrelated hard links remain and are rejected below.
38
+ */
39
+ function recoverPublishedPrivateFileStage(path: string): void {
40
+ if (process.platform === "win32") return;
41
+ const finalStats = lstatSync(path);
42
+ if (finalStats.isSymbolicLink() || !finalStats.isFile() || finalStats.nlink <= 1) return;
43
+
44
+ const dir = dirname(path);
45
+ const prefix = `.${basename(path)}.`;
46
+ const candidates: string[] = [];
47
+ for (const name of readdirSync(dir)) {
48
+ if (!name.startsWith(prefix) || !isPrivateFileStageName(name)) continue;
49
+ const stagePath = join(dir, name);
50
+ try {
51
+ const stageStats = lstatSync(stagePath);
52
+ if (
53
+ stageStats.isFile()
54
+ && !stageStats.isSymbolicLink()
55
+ && stageStats.dev === finalStats.dev
56
+ && stageStats.ino === finalStats.ino
57
+ ) {
58
+ candidates.push(stagePath);
59
+ }
60
+ } catch (error) {
61
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
62
+ }
63
+ }
64
+ if (candidates.length === 0) return;
65
+
66
+ // The final directory entry already exists. Make that entry durable before deleting
67
+ // the recovery witness. A real fsync failure propagates and the strict read stays closed.
68
+ let dirFd: number | null = null;
69
+ try {
70
+ dirFd = openSync(dir, fsConstants.O_RDONLY);
71
+ fsyncSync(dirFd);
72
+ } finally {
73
+ if (dirFd !== null) closeSync(dirFd);
74
+ }
75
+
76
+ for (const stagePath of candidates) {
77
+ try {
78
+ const stageStats = lstatSync(stagePath);
79
+ if (
80
+ stageStats.isFile()
81
+ && !stageStats.isSymbolicLink()
82
+ && stageStats.dev === finalStats.dev
83
+ && stageStats.ino === finalStats.ino
84
+ ) {
85
+ unlinkSync(stagePath);
86
+ }
87
+ } catch (error) {
88
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
89
+ }
90
+ }
91
+ }
92
+
93
+ function withPrivateRegularFile<T>(
94
+ path: string,
95
+ options: PrivateRegularFileReadOptions,
96
+ consume: (fd: number, size: number) => T,
97
+ ): T {
98
+ let pathStats = lstatSync(path);
99
+ if (!pathStats.isSymbolicLink() && pathStats.isFile() && pathStats.nlink > 1) {
100
+ recoverPublishedPrivateFileStage(path);
101
+ pathStats = lstatSync(path);
102
+ }
103
+ if (pathStats.isSymbolicLink() || !pathStats.isFile() || pathStats.nlink !== 1) {
104
+ throw new PublicEvidenceValidationError(options.errorCode, options.errorMessage);
105
+ }
106
+ if (pathStats.size > options.maxBytes) throw sizeError(options);
107
+
108
+ const fd = openSync(path, fsConstants.O_RDONLY | O_NOFOLLOW);
109
+ try {
110
+ const stats = fstatSync(fd);
111
+ if (
112
+ !stats.isFile()
113
+ || stats.nlink !== 1
114
+ || stats.dev !== pathStats.dev
115
+ || stats.ino !== pathStats.ino
116
+ ) {
117
+ throw new PublicEvidenceValidationError(options.errorCode, options.errorMessage);
118
+ }
119
+ if (stats.size > options.maxBytes) throw sizeError(options);
120
+ if (options.requireMode600 && process.platform !== "win32" && (stats.mode & 0o777) !== 0o600) {
121
+ throw new PublicEvidenceValidationError(options.errorCode, options.errorMessage);
122
+ }
123
+ return consume(fd, stats.size);
124
+ } finally {
125
+ closeSync(fd);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Inspect a file only after proving that the pathname and checked descriptor refer to
131
+ * the same private regular file. This keeps quota scans descriptor-bound without
132
+ * reading every cached object into memory.
133
+ */
134
+ export function privateRegularFileSize(
135
+ path: string,
136
+ options: PrivateRegularFileReadOptions,
137
+ ): number {
138
+ return withPrivateRegularFile(path, options, (_fd, size) => size);
139
+ }
140
+
141
+ /**
142
+ * Read bytes only after proving that the pathname and the consumed descriptor refer to
143
+ * the same private regular file. The lstat/dev+ino comparison keeps the protection on
144
+ * platforms where O_NOFOLLOW is unavailable instead of silently following a symlink.
145
+ */
146
+ export function readPrivateRegularFile(
147
+ path: string,
148
+ options: PrivateRegularFileReadOptions,
149
+ ): Buffer {
150
+ return withPrivateRegularFile(path, options, (fd) => {
151
+ const bytes = readFileSync(fd);
152
+ if (bytes.byteLength > options.maxBytes) throw sizeError(options);
153
+ return bytes;
154
+ });
155
+ }
@@ -0,0 +1,26 @@
1
+ import { domainHash, jcsStringify } from "../digest";
2
+
3
+ export type PublicEvidenceIdKind =
4
+ | "subject"
5
+ | "record"
6
+ | "bundle"
7
+ | "bundle_digest"
8
+ | "artifact"
9
+ | "publisher_key"
10
+ | "revocation"
11
+ | "route_registry";
12
+
13
+ const PUBLIC_EVIDENCE_DOMAIN: Record<PublicEvidenceIdKind, string> = {
14
+ subject: "ocx-lab-public:subject:v1",
15
+ record: "ocx-lab-public:record:v1",
16
+ bundle: "ocx-lab-public:bundle:v1",
17
+ bundle_digest: "ocx-lab-public:bundle-digest:v1",
18
+ artifact: "ocx-lab-public:artifact:v1",
19
+ publisher_key: "ocx-lab-public:publisher-key:v1",
20
+ revocation: "ocx-lab-public:revocation:v1",
21
+ route_registry: "ocx-lab-public:route-registry:v1",
22
+ };
23
+
24
+ export function publicEvidenceId(kind: PublicEvidenceIdKind, payload: unknown): string {
25
+ return domainHash(PUBLIC_EVIDENCE_DOMAIN[kind], jcsStringify(payload));
26
+ }
@@ -0,0 +1,16 @@
1
+ export * from "./types";
2
+ export * from "./ids";
3
+ export * from "./registry";
4
+ export * from "./validate";
5
+ export * from "./privacy";
6
+ export * from "./project";
7
+ export * from "./bundle";
8
+ export * from "./signature";
9
+ export * from "./storage";
10
+ export * from "./community-authority";
11
+ export * from "./revocation";
12
+ export * from "./community";
13
+ export * from "./strict-json";
14
+ export * from "./origin";
15
+ export * from "./operator";
16
+ export * from "./purge";