mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { digestCanonicalValue } from "./canonicalDigest.js";
|
|
import { jsonObjectSchema, type JsonValue } from "./jsonValue.js";
|
|
import { digestSchema } from "./../domain/digests.js";
|
|
|
|
/** Concrete provider identity committed by an analysis profile. */
|
|
export const committedProviderSchema = z.strictObject({
|
|
id: z.string().min(1),
|
|
name: z.string().min(1),
|
|
version: z.string().min(1),
|
|
});
|
|
|
|
const unsignedAnalysisProfileSchema = z.strictObject({
|
|
provider: committedProviderSchema,
|
|
parameters: jsonObjectSchema,
|
|
});
|
|
|
|
/** Canonical, provider-neutral commitment to analysis-affecting semantics. */
|
|
export const analysisProfileSchema = unsignedAnalysisProfileSchema
|
|
.extend({ digest: digestSchema })
|
|
.superRefine((profile, context) => {
|
|
const { digest: _digest, ...unsigned } = profile;
|
|
if (profileDigest(unsigned) !== profile.digest)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["digest"],
|
|
message: "Analysis profile digest does not match its parameters",
|
|
});
|
|
});
|
|
|
|
export type CommittedProviderIdentity = z.infer<typeof committedProviderSchema>;
|
|
export type AnalysisProfileCommitment = z.infer<typeof analysisProfileSchema>;
|
|
|
|
/** Create a validated RFC 8785 profile commitment at a provider boundary. */
|
|
export const createAnalysisProfile = (
|
|
provider: CommittedProviderIdentity,
|
|
parameters: Readonly<Record<string, JsonValue>>,
|
|
): AnalysisProfileCommitment => {
|
|
const unsigned = unsignedAnalysisProfileSchema.parse({
|
|
provider: {
|
|
id: provider.id,
|
|
name: provider.name,
|
|
version: provider.version,
|
|
},
|
|
parameters,
|
|
});
|
|
return analysisProfileSchema.parse({
|
|
...unsigned,
|
|
digest: profileDigest(unsigned),
|
|
});
|
|
};
|
|
|
|
/** Compare two already-validated commitments without interpreting parameters. */
|
|
export const analysisProfilesEqual = (
|
|
left: AnalysisProfileCommitment,
|
|
right: AnalysisProfileCommitment,
|
|
): boolean =>
|
|
left.digest === right.digest &&
|
|
left.provider.id === right.provider.id &&
|
|
left.provider.name === right.provider.name &&
|
|
left.provider.version === right.provider.version;
|
|
|
|
const profileDigest = (
|
|
profile: z.infer<typeof unsignedAnalysisProfileSchema>,
|
|
): string => digestCanonicalValue(profile, "Analysis profile");
|