betterwithage Claude Opus 4.7 commited on
Commit
b40fdf5
·
verified ·
1 Parent(s): 723a007

deploy(hf): sync szl-holdings/a11oy@b75f66668940763d83dc6f7b9dfcbc793f7f4fd5 derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy b75f66668940763d83dc6f7b9dfcbc793f7f4fd5.
Files: 1169 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

packages/receipt-substrate/src/index.ts CHANGED
@@ -362,3 +362,617 @@ export function readReceiptJsonl(path: string): OperationalReceipt[] {
362
  export function appendReceiptJsonl(path: string, receipt: OperationalReceipt): void {
363
  fs.appendFileSync(path, receiptToJsonl(receipt), "utf8");
364
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  export function appendReceiptJsonl(path: string, receipt: OperationalReceipt): void {
363
  fs.appendFileSync(path, receiptToJsonl(receipt), "utf8");
364
  }
365
+
366
+ export const TWO_WITNESS_LIVE_ADAPTERS_ENABLED = false as const;
367
+
368
+ export type ResearchEvidenceLabel =
369
+ | "CORROBORATED"
370
+ | "DIVERGENT"
371
+ | "SINGLE_PROVIDER"
372
+ | "INSUFFICIENT"
373
+ | "UNAVAILABLE";
374
+
375
+ export type ResearchProvider = "openai" | "perplexity";
376
+ export type ResearchProviderStatus = "SUCCESS" | "UNAVAILABLE" | "ERROR";
377
+
378
+ export interface ResearchUsageInput {
379
+ readonly input_tokens?: number;
380
+ readonly output_tokens?: number;
381
+ readonly total_tokens?: number;
382
+ readonly reasoning_tokens?: number;
383
+ readonly cached_tokens?: number;
384
+ readonly search_queries?: number;
385
+ }
386
+
387
+ export interface OpenAIResearchSourceInput {
388
+ readonly url: string;
389
+ readonly title?: string;
390
+ readonly published_at?: string;
391
+ readonly last_updated_at?: string;
392
+ }
393
+
394
+ export interface PerplexityResearchResultInput {
395
+ readonly url: string;
396
+ readonly title?: string;
397
+ readonly date?: string;
398
+ readonly last_updated?: string;
399
+ }
400
+
401
+ export interface OpenAIWebSearchResultInput {
402
+ readonly provider: "openai";
403
+ readonly status: ResearchProviderStatus;
404
+ readonly query_sha256: string;
405
+ readonly policy_sha256: string;
406
+ readonly response_id?: string;
407
+ readonly model?: string;
408
+ readonly http_status?: number;
409
+ readonly latency_ms?: number;
410
+ readonly usage?: ResearchUsageInput;
411
+ readonly cost_usd?: number;
412
+ readonly sources?: readonly OpenAIResearchSourceInput[];
413
+ }
414
+
415
+ export interface PerplexitySearchResultInput {
416
+ readonly provider: "perplexity";
417
+ readonly status: ResearchProviderStatus;
418
+ readonly query_sha256: string;
419
+ readonly policy_sha256: string;
420
+ readonly response_id?: string;
421
+ readonly model?: string;
422
+ readonly http_status?: number;
423
+ readonly latency_ms?: number;
424
+ readonly usage?: ResearchUsageInput;
425
+ readonly cost_usd?: number;
426
+ readonly results?: readonly PerplexityResearchResultInput[];
427
+ }
428
+
429
+ export interface NormalizedResearchSource {
430
+ readonly url: string;
431
+ readonly domain: string;
432
+ readonly title_sha256?: string;
433
+ readonly published_at?: string;
434
+ readonly last_updated_at?: string;
435
+ }
436
+
437
+ export interface NormalizedResearchUsage {
438
+ readonly input_tokens?: number;
439
+ readonly output_tokens?: number;
440
+ readonly total_tokens?: number;
441
+ readonly reasoning_tokens?: number;
442
+ readonly cached_tokens?: number;
443
+ readonly search_queries?: number;
444
+ }
445
+
446
+ export interface NormalizedResearchProviderEvidence {
447
+ readonly schema_version: "a11oy.research_provider_evidence/v0";
448
+ readonly provider: ResearchProvider;
449
+ readonly api_surface: "openai.responses.web_search" | "perplexity.search";
450
+ readonly tool: "web_search" | "search";
451
+ readonly status: ResearchProviderStatus;
452
+ readonly query_sha256: string;
453
+ readonly policy_sha256: string;
454
+ readonly response_id?: string;
455
+ readonly model?: string;
456
+ readonly http_status?: number;
457
+ readonly caller_observed_latency_ms?: number;
458
+ readonly usage?: NormalizedResearchUsage;
459
+ readonly provider_reported_cost_usd?: number;
460
+ readonly sources: readonly NormalizedResearchSource[];
461
+ readonly source_count: number;
462
+ readonly source_list_sha256: string;
463
+ }
464
+
465
+ export interface ResearchEvidenceComparison {
466
+ readonly schema_version: "a11oy.research_evidence_comparison/v0";
467
+ readonly label: ResearchEvidenceLabel;
468
+ readonly query_sha256: string;
469
+ readonly policy_sha256: string;
470
+ readonly providers: readonly NormalizedResearchProviderEvidence[];
471
+ readonly successful_provider_count: number;
472
+ readonly evidence_provider_count: number;
473
+ readonly source_union_count: number;
474
+ readonly source_url_overlap_count: number;
475
+ readonly source_domain_overlap_count: number;
476
+ readonly source_url_jaccard: number;
477
+ readonly integrity_valid: boolean;
478
+ readonly integrity_errors: readonly string[];
479
+ readonly action_authorized: false;
480
+ }
481
+
482
+ export interface TwoWitnessResearchReceiptOptions extends EmitReceiptOptions {
483
+ readonly actor_id: string;
484
+ readonly invocation_id?: string;
485
+ }
486
+
487
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
488
+ const SENSITIVE_SOURCE_QUERY_KEYS = new Set([
489
+ "access_token",
490
+ "api_key",
491
+ "apikey",
492
+ "auth",
493
+ "authorization",
494
+ "credential",
495
+ "key",
496
+ "password",
497
+ "secret",
498
+ "signature",
499
+ "sig",
500
+ "token",
501
+ ]);
502
+ const DIGESTED_SOURCE_QUERY_KEYS = new Set(["code"]);
503
+ const SENSITIVE_SOURCE_QUERY_PREFIXES = ["x-amz-", "x-goog-", "x-oss-"];
504
+ const TRACKING_SOURCE_QUERY_PREFIXES = ["utm_"];
505
+ const TRACKING_SOURCE_QUERY_KEYS = new Set(["fbclid", "gclid", "mc_cid", "mc_eid"]);
506
+
507
+ function cleanOptionalText(value: unknown, maxLength = 256): string | undefined {
508
+ if (typeof value !== "string") return undefined;
509
+ const cleaned = value.normalize("NFC").trim();
510
+ if (!cleaned || cleaned.length > maxLength || /[\r\n]/.test(cleaned)) return undefined;
511
+ return cleaned;
512
+ }
513
+
514
+ function cleanSha256(value: unknown): string {
515
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
516
+ }
517
+
518
+ function cleanStatus(value: unknown): ResearchProviderStatus {
519
+ if (value === "SUCCESS" || value === "UNAVAILABLE" || value === "ERROR") return value;
520
+ return "ERROR";
521
+ }
522
+
523
+ function cleanNonNegativeNumber(value: unknown): number | undefined {
524
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
525
+ return value;
526
+ }
527
+
528
+ function cleanNonNegativeInteger(value: unknown): number | undefined {
529
+ const cleaned = cleanNonNegativeNumber(value);
530
+ return cleaned !== undefined && Number.isInteger(cleaned) ? cleaned : undefined;
531
+ }
532
+
533
+ function cleanHttpStatus(value: unknown): number | undefined {
534
+ const cleaned = cleanNonNegativeInteger(value);
535
+ return cleaned !== undefined && cleaned >= 100 && cleaned <= 599 ? cleaned : undefined;
536
+ }
537
+
538
+ function cleanDate(value: unknown): string | undefined {
539
+ if (typeof value !== "string" || !value.trim()) return undefined;
540
+ const date = new Date(value);
541
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
542
+ }
543
+
544
+ function cleanSourceUrl(raw: unknown): string | undefined {
545
+ if (typeof raw !== "string") return undefined;
546
+ try {
547
+ const url = new URL(raw.trim());
548
+ if ((url.protocol !== "http:" && url.protocol !== "https:") || url.username || url.password) {
549
+ return undefined;
550
+ }
551
+
552
+ url.hash = "";
553
+ for (const key of [...url.searchParams.keys()]) {
554
+ const lower = key.toLowerCase();
555
+ if (DIGESTED_SOURCE_QUERY_KEYS.has(lower)) {
556
+ const digests = url.searchParams
557
+ .getAll(key)
558
+ .map((value) => (
559
+ /^sha256:[a-f0-9]{64}$/.test(value)
560
+ ? value.slice("sha256:".length)
561
+ : hashHex(value, "SHA-256")
562
+ ))
563
+ .sort();
564
+ url.searchParams.delete(key);
565
+ for (const digest of digests) {
566
+ url.searchParams.append(key, `sha256:${digest}`);
567
+ }
568
+ continue;
569
+ }
570
+ if (
571
+ SENSITIVE_SOURCE_QUERY_KEYS.has(lower)
572
+ || SENSITIVE_SOURCE_QUERY_PREFIXES.some((prefix) => lower.startsWith(prefix))
573
+ || TRACKING_SOURCE_QUERY_KEYS.has(lower)
574
+ || TRACKING_SOURCE_QUERY_PREFIXES.some((prefix) => lower.startsWith(prefix))
575
+ ) {
576
+ url.searchParams.delete(key);
577
+ }
578
+ }
579
+ url.searchParams.sort();
580
+ url.hostname = url.hostname.toLowerCase();
581
+ if ((url.protocol === "http:" && url.port === "80") || (url.protocol === "https:" && url.port === "443")) {
582
+ url.port = "";
583
+ }
584
+ if (url.pathname.length > 1 && url.pathname.endsWith("/")) {
585
+ url.pathname = url.pathname.replace(/\/+$/, "");
586
+ }
587
+ return url.toString();
588
+ } catch {
589
+ return undefined;
590
+ }
591
+ }
592
+
593
+ function normalizeResearchSources(
594
+ inputs: readonly {
595
+ readonly url: string;
596
+ readonly title?: string;
597
+ readonly published_at?: string;
598
+ readonly last_updated_at?: string;
599
+ }[],
600
+ ): readonly NormalizedResearchSource[] {
601
+ const byUrl = new Map<string, NormalizedResearchSource>();
602
+ for (const input of inputs) {
603
+ const url = cleanSourceUrl(input.url);
604
+ if (!url) continue;
605
+ const title = cleanOptionalText(input.title, 2_000);
606
+ const domain = new URL(url).hostname;
607
+ const source: NormalizedResearchSource = {
608
+ url,
609
+ domain,
610
+ ...(title ? { title_sha256: hashHex(title, "SHA-256") } : {}),
611
+ ...(cleanDate(input.published_at) ? { published_at: cleanDate(input.published_at) } : {}),
612
+ ...(cleanDate(input.last_updated_at) ? { last_updated_at: cleanDate(input.last_updated_at) } : {}),
613
+ };
614
+ byUrl.set(url, source);
615
+ }
616
+ return [...byUrl.values()].sort((left, right) => left.url.localeCompare(right.url));
617
+ }
618
+
619
+ function normalizeResearchUsage(input: ResearchUsageInput | undefined): NormalizedResearchUsage | undefined {
620
+ if (!input) return undefined;
621
+ const usage: NormalizedResearchUsage = {
622
+ ...(cleanNonNegativeInteger(input.input_tokens) !== undefined
623
+ ? { input_tokens: cleanNonNegativeInteger(input.input_tokens) }
624
+ : {}),
625
+ ...(cleanNonNegativeInteger(input.output_tokens) !== undefined
626
+ ? { output_tokens: cleanNonNegativeInteger(input.output_tokens) }
627
+ : {}),
628
+ ...(cleanNonNegativeInteger(input.total_tokens) !== undefined
629
+ ? { total_tokens: cleanNonNegativeInteger(input.total_tokens) }
630
+ : {}),
631
+ ...(cleanNonNegativeInteger(input.reasoning_tokens) !== undefined
632
+ ? { reasoning_tokens: cleanNonNegativeInteger(input.reasoning_tokens) }
633
+ : {}),
634
+ ...(cleanNonNegativeInteger(input.cached_tokens) !== undefined
635
+ ? { cached_tokens: cleanNonNegativeInteger(input.cached_tokens) }
636
+ : {}),
637
+ ...(cleanNonNegativeInteger(input.search_queries) !== undefined
638
+ ? { search_queries: cleanNonNegativeInteger(input.search_queries) }
639
+ : {}),
640
+ };
641
+ return Object.keys(usage).length > 0 ? usage : undefined;
642
+ }
643
+
644
+ function projectNormalizedResearchSource(
645
+ input: NormalizedResearchSource,
646
+ ): NormalizedResearchSource {
647
+ return {
648
+ url: input.url,
649
+ domain: input.domain,
650
+ ...(input.title_sha256 !== undefined ? { title_sha256: input.title_sha256 } : {}),
651
+ ...(input.published_at !== undefined ? { published_at: input.published_at } : {}),
652
+ ...(input.last_updated_at !== undefined
653
+ ? { last_updated_at: input.last_updated_at }
654
+ : {}),
655
+ };
656
+ }
657
+
658
+ function projectNormalizedResearchUsage(
659
+ input: NormalizedResearchUsage | undefined,
660
+ ): NormalizedResearchUsage | undefined {
661
+ if (!input) return undefined;
662
+ const projected: NormalizedResearchUsage = {
663
+ ...(input.input_tokens !== undefined ? { input_tokens: input.input_tokens } : {}),
664
+ ...(input.output_tokens !== undefined ? { output_tokens: input.output_tokens } : {}),
665
+ ...(input.total_tokens !== undefined ? { total_tokens: input.total_tokens } : {}),
666
+ ...(input.reasoning_tokens !== undefined
667
+ ? { reasoning_tokens: input.reasoning_tokens }
668
+ : {}),
669
+ ...(input.cached_tokens !== undefined ? { cached_tokens: input.cached_tokens } : {}),
670
+ ...(input.search_queries !== undefined
671
+ ? { search_queries: input.search_queries }
672
+ : {}),
673
+ };
674
+ return Object.keys(projected).length > 0 ? projected : undefined;
675
+ }
676
+
677
+ function projectNormalizedProviderEvidence(
678
+ input: NormalizedResearchProviderEvidence,
679
+ ): NormalizedResearchProviderEvidence {
680
+ const usage = projectNormalizedResearchUsage(input.usage);
681
+ return {
682
+ schema_version: input.schema_version,
683
+ provider: input.provider,
684
+ api_surface: input.api_surface,
685
+ tool: input.tool,
686
+ status: input.status,
687
+ query_sha256: input.query_sha256,
688
+ policy_sha256: input.policy_sha256,
689
+ ...(input.response_id !== undefined ? { response_id: input.response_id } : {}),
690
+ ...(input.model !== undefined ? { model: input.model } : {}),
691
+ ...(input.http_status !== undefined ? { http_status: input.http_status } : {}),
692
+ ...(input.caller_observed_latency_ms !== undefined
693
+ ? { caller_observed_latency_ms: input.caller_observed_latency_ms }
694
+ : {}),
695
+ ...(usage ? { usage } : {}),
696
+ ...(input.provider_reported_cost_usd !== undefined
697
+ ? { provider_reported_cost_usd: input.provider_reported_cost_usd }
698
+ : {}),
699
+ sources: input.sources.map(projectNormalizedResearchSource),
700
+ source_count: input.source_count,
701
+ source_list_sha256: input.source_list_sha256,
702
+ };
703
+ }
704
+
705
+ function normalizeProviderEvidence(input: {
706
+ readonly provider: ResearchProvider;
707
+ readonly api_surface: NormalizedResearchProviderEvidence["api_surface"];
708
+ readonly tool: NormalizedResearchProviderEvidence["tool"];
709
+ readonly status: ResearchProviderStatus;
710
+ readonly query_sha256: string;
711
+ readonly policy_sha256: string;
712
+ readonly response_id?: string;
713
+ readonly model?: string;
714
+ readonly http_status?: number;
715
+ readonly latency_ms?: number;
716
+ readonly usage?: ResearchUsageInput;
717
+ readonly cost_usd?: number;
718
+ readonly sources: readonly NormalizedResearchSource[];
719
+ }): NormalizedResearchProviderEvidence {
720
+ const responseId = cleanOptionalText(input.response_id);
721
+ const model = cleanOptionalText(input.model);
722
+ const httpStatus = cleanHttpStatus(input.http_status);
723
+ const latencyMs = cleanNonNegativeNumber(input.latency_ms);
724
+ const usage = normalizeResearchUsage(input.usage);
725
+ const costUsd = cleanNonNegativeNumber(input.cost_usd);
726
+ return {
727
+ schema_version: "a11oy.research_provider_evidence/v0",
728
+ provider: input.provider,
729
+ api_surface: input.api_surface,
730
+ tool: input.tool,
731
+ status: cleanStatus(input.status),
732
+ query_sha256: cleanSha256(input.query_sha256),
733
+ policy_sha256: cleanSha256(input.policy_sha256),
734
+ ...(responseId ? { response_id: responseId } : {}),
735
+ ...(model ? { model } : {}),
736
+ ...(httpStatus !== undefined ? { http_status: httpStatus } : {}),
737
+ ...(latencyMs !== undefined ? { caller_observed_latency_ms: latencyMs } : {}),
738
+ ...(usage ? { usage } : {}),
739
+ ...(costUsd !== undefined ? { provider_reported_cost_usd: costUsd } : {}),
740
+ sources: input.sources,
741
+ source_count: input.sources.length,
742
+ source_list_sha256: hashHex(input.sources, "SHA-256"),
743
+ };
744
+ }
745
+
746
+ export function normalizeOpenAIWebSearchResult(
747
+ input: OpenAIWebSearchResultInput,
748
+ ): NormalizedResearchProviderEvidence {
749
+ return normalizeProviderEvidence({
750
+ provider: "openai",
751
+ api_surface: "openai.responses.web_search",
752
+ tool: "web_search",
753
+ status: input.status,
754
+ query_sha256: input.query_sha256,
755
+ policy_sha256: input.policy_sha256,
756
+ response_id: input.response_id,
757
+ model: input.model,
758
+ http_status: input.http_status,
759
+ latency_ms: input.latency_ms,
760
+ usage: input.usage,
761
+ cost_usd: input.cost_usd,
762
+ sources: normalizeResearchSources(input.sources ?? []),
763
+ });
764
+ }
765
+
766
+ export function normalizePerplexitySearchResult(
767
+ input: PerplexitySearchResultInput,
768
+ ): NormalizedResearchProviderEvidence {
769
+ return normalizeProviderEvidence({
770
+ provider: "perplexity",
771
+ api_surface: "perplexity.search",
772
+ tool: "search",
773
+ status: input.status,
774
+ query_sha256: input.query_sha256,
775
+ policy_sha256: input.policy_sha256,
776
+ response_id: input.response_id,
777
+ model: input.model,
778
+ http_status: input.http_status,
779
+ latency_ms: input.latency_ms,
780
+ usage: input.usage,
781
+ cost_usd: input.cost_usd,
782
+ sources: normalizeResearchSources(
783
+ (input.results ?? []).map((result) => ({
784
+ url: result.url,
785
+ title: result.title,
786
+ published_at: result.date,
787
+ last_updated_at: result.last_updated,
788
+ })),
789
+ ),
790
+ });
791
+ }
792
+
793
+ function intersectionSize(left: ReadonlySet<string>, right: ReadonlySet<string>): number {
794
+ let count = 0;
795
+ for (const value of left) {
796
+ if (right.has(value)) count += 1;
797
+ }
798
+ return count;
799
+ }
800
+
801
+ export function compareResearchEvidence(input: {
802
+ readonly query_sha256: string;
803
+ readonly policy_sha256: string;
804
+ readonly providers: readonly NormalizedResearchProviderEvidence[];
805
+ }): ResearchEvidenceComparison {
806
+ const querySha256 = cleanSha256(input.query_sha256);
807
+ const policySha256 = cleanSha256(input.policy_sha256);
808
+ const providers = input.providers
809
+ .map(projectNormalizedProviderEvidence)
810
+ .sort((left, right) => left.provider.localeCompare(right.provider));
811
+ const errors: string[] = [];
812
+
813
+ if (!SHA256_PATTERN.test(querySha256)) errors.push("expected query_sha256 is not a SHA-256 digest");
814
+ if (!SHA256_PATTERN.test(policySha256)) errors.push("expected policy_sha256 is not a SHA-256 digest");
815
+
816
+ const expectedProviders = new Set<ResearchProvider>(["openai", "perplexity"]);
817
+ const seenProviders = new Set<ResearchProvider>();
818
+ for (const provider of providers) {
819
+ if (seenProviders.has(provider.provider)) errors.push(`duplicate provider: ${provider.provider}`);
820
+ seenProviders.add(provider.provider);
821
+ if (provider.schema_version !== "a11oy.research_provider_evidence/v0") {
822
+ errors.push(`${provider.provider}: schema_version mismatch`);
823
+ }
824
+ const expectedSurface = provider.provider === "openai"
825
+ ? "openai.responses.web_search"
826
+ : "perplexity.search";
827
+ const expectedTool = provider.provider === "openai" ? "web_search" : "search";
828
+ if (provider.api_surface !== expectedSurface) {
829
+ errors.push(`${provider.provider}: api_surface mismatch`);
830
+ }
831
+ if (provider.tool !== expectedTool) errors.push(`${provider.provider}: tool mismatch`);
832
+ if (!SHA256_PATTERN.test(provider.query_sha256)) {
833
+ errors.push(`${provider.provider}: query_sha256 is not a SHA-256 digest`);
834
+ } else if (provider.query_sha256 !== querySha256) {
835
+ errors.push(`${provider.provider}: query_sha256 mismatch`);
836
+ }
837
+ if (!SHA256_PATTERN.test(provider.policy_sha256)) {
838
+ errors.push(`${provider.provider}: policy_sha256 is not a SHA-256 digest`);
839
+ } else if (provider.policy_sha256 !== policySha256) {
840
+ errors.push(`${provider.provider}: policy_sha256 mismatch`);
841
+ }
842
+ if (provider.source_count !== provider.sources.length) {
843
+ errors.push(`${provider.provider}: source_count mismatch`);
844
+ }
845
+ if (provider.source_list_sha256 !== hashHex(provider.sources, "SHA-256")) {
846
+ errors.push(`${provider.provider}: source_list_sha256 mismatch`);
847
+ }
848
+ if (provider.status !== "SUCCESS" && provider.sources.length > 0) {
849
+ errors.push(`${provider.provider}: non-success status carries evidence`);
850
+ }
851
+ if (
852
+ provider.status === "SUCCESS"
853
+ && provider.http_status !== undefined
854
+ && (provider.http_status < 200 || provider.http_status >= 300)
855
+ ) {
856
+ errors.push(`${provider.provider}: success status conflicts with http_status`);
857
+ }
858
+ for (const [index, source] of provider.sources.entries()) {
859
+ if (cleanSourceUrl(source.url) !== source.url) {
860
+ errors.push(`${provider.provider}: source ${index} URL is not canonical`);
861
+ }
862
+ try {
863
+ if (new URL(source.url).hostname !== source.domain) {
864
+ errors.push(`${provider.provider}: source ${index} domain mismatch`);
865
+ }
866
+ } catch {
867
+ errors.push(`${provider.provider}: source ${index} URL is invalid`);
868
+ }
869
+ if (source.title_sha256 !== undefined && !SHA256_PATTERN.test(source.title_sha256)) {
870
+ errors.push(`${provider.provider}: source ${index} title_sha256 is invalid`);
871
+ }
872
+ }
873
+ }
874
+ for (const provider of expectedProviders) {
875
+ if (!seenProviders.has(provider)) errors.push(`missing provider: ${provider}`);
876
+ }
877
+
878
+ const openai = providers.find((provider) => provider.provider === "openai");
879
+ const perplexity = providers.find((provider) => provider.provider === "perplexity");
880
+ const openaiUrls = new Set(openai?.sources.map((source) => source.url) ?? []);
881
+ const perplexityUrls = new Set(perplexity?.sources.map((source) => source.url) ?? []);
882
+ const openaiDomains = new Set(openai?.sources.map((source) => source.domain) ?? []);
883
+ const perplexityDomains = new Set(perplexity?.sources.map((source) => source.domain) ?? []);
884
+ const sourceUrlOverlapCount = intersectionSize(openaiUrls, perplexityUrls);
885
+ const sourceDomainOverlapCount = intersectionSize(openaiDomains, perplexityDomains);
886
+ const sourceUnion = new Set([...openaiUrls, ...perplexityUrls]);
887
+ const successfulProviders = providers.filter((provider) => provider.status === "SUCCESS");
888
+ const evidenceProviders = successfulProviders.filter((provider) => provider.source_count > 0);
889
+
890
+ let label: ResearchEvidenceLabel;
891
+ if (errors.length > 0) {
892
+ label = "INSUFFICIENT";
893
+ } else if (successfulProviders.length === 0) {
894
+ label = "UNAVAILABLE";
895
+ } else if (evidenceProviders.length === 0 || evidenceProviders.length !== successfulProviders.length) {
896
+ label = "INSUFFICIENT";
897
+ } else if (successfulProviders.length === 1) {
898
+ label = "SINGLE_PROVIDER";
899
+ } else if (sourceUrlOverlapCount > 0) {
900
+ label = "CORROBORATED";
901
+ } else {
902
+ label = "DIVERGENT";
903
+ }
904
+
905
+ return {
906
+ schema_version: "a11oy.research_evidence_comparison/v0",
907
+ label,
908
+ query_sha256: querySha256,
909
+ policy_sha256: policySha256,
910
+ providers,
911
+ successful_provider_count: successfulProviders.length,
912
+ evidence_provider_count: evidenceProviders.length,
913
+ source_union_count: sourceUnion.size,
914
+ source_url_overlap_count: sourceUrlOverlapCount,
915
+ source_domain_overlap_count: sourceDomainOverlapCount,
916
+ source_url_jaccard: sourceUnion.size === 0
917
+ ? 0
918
+ : Number((sourceUrlOverlapCount / sourceUnion.size).toFixed(6)),
919
+ integrity_valid: errors.length === 0,
920
+ integrity_errors: errors.sort(),
921
+ action_authorized: false,
922
+ };
923
+ }
924
+
925
+ export function emitTwoWitnessResearchReceipt(
926
+ comparison: ResearchEvidenceComparison,
927
+ options: TwoWitnessResearchReceiptOptions,
928
+ ): OperationalReceipt {
929
+ const verifiedComparison = compareResearchEvidence({
930
+ query_sha256: comparison.query_sha256,
931
+ policy_sha256: comparison.policy_sha256,
932
+ providers: comparison.providers,
933
+ });
934
+ if (canonicalJson(verifiedComparison) !== canonicalJson(comparison)) {
935
+ throw new Error("research evidence comparison verification failed");
936
+ }
937
+ const payload = {
938
+ schema_version: "a11oy.two_witness_research_receipt/v0",
939
+ live_adapters_enabled: TWO_WITNESS_LIVE_ADAPTERS_ENABLED,
940
+ signature_state: "UNSIGNED_LOCAL",
941
+ external_attestation: false,
942
+ action_authorized: false,
943
+ query_sha256: comparison.query_sha256,
944
+ policy_sha256: comparison.policy_sha256,
945
+ label: comparison.label,
946
+ integrity_valid: comparison.integrity_valid,
947
+ integrity_errors: comparison.integrity_errors,
948
+ successful_provider_count: comparison.successful_provider_count,
949
+ evidence_provider_count: comparison.evidence_provider_count,
950
+ source_union_count: comparison.source_union_count,
951
+ source_url_overlap_count: comparison.source_url_overlap_count,
952
+ source_domain_overlap_count: comparison.source_domain_overlap_count,
953
+ source_url_jaccard: comparison.source_url_jaccard,
954
+ providers: comparison.providers,
955
+ };
956
+ const envelope = createToolEnvelope({
957
+ protocol: "a11oy",
958
+ actor_id: options.actor_id,
959
+ tool_name: "two_witness_research_compare_v0",
960
+ tool_version: "0.1.0",
961
+ invocation_id: options.invocation_id,
962
+ lambda_axes: ["provenance", "restraint"],
963
+ payload,
964
+ metadata: {
965
+ network_access: "DISABLED",
966
+ provider_credentials: "NOT_ACCEPTED",
967
+ live_adapter_feature_flag: "OFF",
968
+ },
969
+ });
970
+ return emitReceipt(envelope, {
971
+ previousReceipt: options.previousReceipt,
972
+ policy: options.policy,
973
+ quorumSignatures: options.quorumSignatures,
974
+ timestamp: options.timestamp,
975
+ sequence: options.sequence,
976
+ eventType: "A11OY_OPERATION",
977
+ });
978
+ }
packages/receipt-substrate/src/research_evidence.test.ts ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import test from "node:test";
4
+
5
+ import {
6
+ TWO_WITNESS_LIVE_ADAPTERS_ENABLED,
7
+ compareResearchEvidence,
8
+ emitTwoWitnessResearchReceipt,
9
+ normalizeOpenAIWebSearchResult,
10
+ normalizePerplexitySearchResult,
11
+ verifyReceipt,
12
+ type NormalizedResearchProviderEvidence,
13
+ type OpenAIWebSearchResultInput,
14
+ type PerplexitySearchResultInput,
15
+ type ResearchEvidenceLabel,
16
+ } from "./index.ts";
17
+
18
+ interface FixtureCase {
19
+ readonly name: string;
20
+ readonly expected_label: ResearchEvidenceLabel;
21
+ readonly expected_integrity_valid: boolean;
22
+ readonly tamper_source_list?: boolean;
23
+ readonly openai: OpenAIWebSearchResultInput;
24
+ readonly perplexity: PerplexitySearchResultInput;
25
+ }
26
+
27
+ interface FixtureDocument {
28
+ readonly query_sha256: string;
29
+ readonly policy_sha256: string;
30
+ readonly cases: readonly FixtureCase[];
31
+ }
32
+
33
+ const fixtureUrl = new URL("../fixtures/two_witness_research_v0.json", import.meta.url);
34
+ const fixtures = JSON.parse(readFileSync(fixtureUrl, "utf8")) as FixtureDocument;
35
+
36
+ function normalizedProviders(fixture: FixtureCase): readonly NormalizedResearchProviderEvidence[] {
37
+ const openai = normalizeOpenAIWebSearchResult(fixture.openai);
38
+ const perplexity = normalizePerplexitySearchResult(fixture.perplexity);
39
+ if (!fixture.tamper_source_list) return [openai, perplexity];
40
+
41
+ return [
42
+ {
43
+ ...openai,
44
+ sources: openai.sources.map((source, index) => (
45
+ index === 0 ? { ...source, url: "https://tamper.example/changed" } : source
46
+ )),
47
+ },
48
+ perplexity,
49
+ ];
50
+ }
51
+
52
+ for (const fixture of fixtures.cases) {
53
+ test(`two-witness fixture: ${fixture.name}`, () => {
54
+ const comparison = compareResearchEvidence({
55
+ query_sha256: fixtures.query_sha256,
56
+ policy_sha256: fixtures.policy_sha256,
57
+ providers: normalizedProviders(fixture),
58
+ });
59
+
60
+ assert.equal(comparison.label, fixture.expected_label);
61
+ assert.equal(comparison.integrity_valid, fixture.expected_integrity_valid);
62
+ assert.equal(comparison.action_authorized, false);
63
+ assert.notEqual(comparison.label, "TRUE");
64
+
65
+ const receipt = emitTwoWitnessResearchReceipt(comparison, {
66
+ actor_id: "did:example:offline-test",
67
+ invocation_id: `fixture-${fixture.name}`,
68
+ timestamp: new Date("2026-07-28T12:00:00.000Z"),
69
+ });
70
+ assert.deepEqual(verifyReceipt(receipt), { valid: true, errors: [] });
71
+
72
+ const serialized = JSON.stringify(receipt.envelope);
73
+ assert.equal(serialized.includes("raw query must not be receipted"), false);
74
+ assert.equal(serialized.includes("raw snippet must not be receipted"), false);
75
+ assert.equal(serialized.includes("must-not-escape"), false);
76
+ assert.equal(serialized.includes("\"snippet\""), false);
77
+ assert.equal(serialized.includes("\"api_key\""), false);
78
+ });
79
+ }
80
+
81
+ test("provider normalizers produce stable source hashes and strip sensitive URL parameters", () => {
82
+ const fixture = fixtures.cases.find((candidate) => candidate.name === "matching");
83
+ assert.ok(fixture);
84
+ const [openai, perplexity] = normalizedProviders(fixture);
85
+
86
+ assert.equal(openai.sources[0]?.url, "https://example.com/research/report");
87
+ assert.equal(perplexity.sources[0]?.url, "https://example.com/research/report");
88
+ assert.equal(openai.source_list_sha256, perplexity.source_list_sha256);
89
+ assert.match(openai.sources[0]?.title_sha256 ?? "", /^[a-f0-9]{64}$/);
90
+ });
91
+
92
+ test("content-selecting code parameters retain distinct redacted identities", () => {
93
+ const base = fixtures.cases.find((candidate) => candidate.name === "matching");
94
+ assert.ok(base);
95
+ const openai = normalizeOpenAIWebSearchResult({
96
+ ...base.openai,
97
+ sources: [{
98
+ url: "https://research.example/paper?code=alpha",
99
+ }],
100
+ });
101
+ const perplexity = normalizePerplexitySearchResult({
102
+ ...base.perplexity,
103
+ results: [{
104
+ url: "https://research.example/paper?code=beta",
105
+ }],
106
+ });
107
+ const comparison = compareResearchEvidence({
108
+ query_sha256: fixtures.query_sha256,
109
+ policy_sha256: fixtures.policy_sha256,
110
+ providers: [openai, perplexity],
111
+ });
112
+
113
+ assert.equal(comparison.source_url_overlap_count, 0);
114
+ assert.equal(comparison.label, "DIVERGENT");
115
+ assert.equal(JSON.stringify(comparison).includes("alpha"), false);
116
+ assert.equal(JSON.stringify(comparison).includes("beta"), false);
117
+ assert.match(new URL(openai.sources[0]?.url ?? "").searchParams.get("code") ?? "", /^sha256:/);
118
+ });
119
+
120
+ test("vendor-prefixed presigned credentials never enter normalized evidence", () => {
121
+ const base = fixtures.cases.find((candidate) => candidate.name === "matching");
122
+ assert.ok(base);
123
+ const openai = normalizeOpenAIWebSearchResult({
124
+ ...base.openai,
125
+ sources: [{
126
+ url: [
127
+ "https://research.example/paper?topic=governance",
128
+ "X-Amz-Credential=must-not-escape",
129
+ "X-Amz-Signature=must-not-escape",
130
+ ].join("&"),
131
+ }],
132
+ });
133
+ const serialized = JSON.stringify(openai);
134
+
135
+ assert.equal(serialized.includes("must-not-escape"), false);
136
+ assert.equal(openai.sources[0]?.url, "https://research.example/paper?topic=governance");
137
+ });
138
+
139
+ test("comparison projects provider and source fields through an explicit allowlist", () => {
140
+ const base = fixtures.cases.find((candidate) => candidate.name === "matching");
141
+ assert.ok(base);
142
+ const openai = normalizeOpenAIWebSearchResult(base.openai);
143
+ const perplexity = normalizePerplexitySearchResult(base.perplexity);
144
+ const unsafeOpenAI = {
145
+ ...openai,
146
+ api_key: "must-not-escape",
147
+ sources: openai.sources.map((source) => ({
148
+ ...source,
149
+ snippet: "raw snippet must not be receipted",
150
+ })),
151
+ } as NormalizedResearchProviderEvidence;
152
+ const comparison = compareResearchEvidence({
153
+ query_sha256: fixtures.query_sha256,
154
+ policy_sha256: fixtures.policy_sha256,
155
+ providers: [unsafeOpenAI, perplexity],
156
+ });
157
+ const receipt = emitTwoWitnessResearchReceipt(comparison, {
158
+ actor_id: "did:example:projection-test",
159
+ });
160
+ const serialized = JSON.stringify(receipt.envelope);
161
+
162
+ assert.equal(serialized.includes("must-not-escape"), false);
163
+ assert.equal(serialized.includes("raw snippet must not be receipted"), false);
164
+ assert.equal(serialized.includes("\"api_key\""), false);
165
+ assert.equal(serialized.includes("\"snippet\""), false);
166
+ });
167
+
168
+ test("live adapters remain disabled and the public label vocabulary excludes a truth claim", () => {
169
+ const labels: readonly ResearchEvidenceLabel[] = [
170
+ "CORROBORATED",
171
+ "DIVERGENT",
172
+ "SINGLE_PROVIDER",
173
+ "INSUFFICIENT",
174
+ "UNAVAILABLE",
175
+ ];
176
+ assert.equal(TWO_WITNESS_LIVE_ADAPTERS_ENABLED, false);
177
+ assert.equal(labels.includes("TRUE" as ResearchEvidenceLabel), false);
178
+ });
179
+
180
+ test("receipt emission rejects a caller-modified comparison", () => {
181
+ const fixture = fixtures.cases.find((candidate) => candidate.name === "disjoint");
182
+ assert.ok(fixture);
183
+ const comparison = compareResearchEvidence({
184
+ query_sha256: fixtures.query_sha256,
185
+ policy_sha256: fixtures.policy_sha256,
186
+ providers: normalizedProviders(fixture),
187
+ });
188
+
189
+ assert.throws(
190
+ () => emitTwoWitnessResearchReceipt(
191
+ { ...comparison, label: "CORROBORATED" },
192
+ { actor_id: "did:example:offline-test" },
193
+ ),
194
+ /comparison verification failed/,
195
+ );
196
+ });