Skip to main content

arete_hash/
kind.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5use crate::HashError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(rename_all = "kebab-case")]
9pub enum CanonicalizationProfile {
10    RawBytesV1,
11    AreteJcsV1,
12    FramedTupleV1,
13    ArtifactTreeV1,
14}
15
16impl CanonicalizationProfile {
17    pub const fn as_str(self) -> &'static str {
18        match self {
19            Self::RawBytesV1 => "raw-bytes-v1",
20            Self::AreteJcsV1 => "arete-jcs-v1",
21            Self::FramedTupleV1 => "framed-tuple-v1",
22            Self::ArtifactTreeV1 => "artifact-tree-v1",
23        }
24    }
25}
26
27impl fmt::Display for CanonicalizationProfile {
28    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29        formatter.write_str(self.as_str())
30    }
31}
32
33impl FromStr for CanonicalizationProfile {
34    type Err = HashError;
35
36    fn from_str(value: &str) -> Result<Self, Self::Err> {
37        match value {
38            "raw-bytes-v1" => Ok(Self::RawBytesV1),
39            "arete-jcs-v1" => Ok(Self::AreteJcsV1),
40            "framed-tuple-v1" => Ok(Self::FramedTupleV1),
41            "artifact-tree-v1" => Ok(Self::ArtifactTreeV1),
42            _ => Err(HashError::InvalidHashId("unknown canonicalization profile")),
43        }
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "kebab-case")]
49pub enum HashKindName {
50    IdlSource,
51    IdlContent,
52    IdlPortable,
53    IdlNormalized,
54    ProgramSpec,
55    AstPortable,
56    RuntimeArtifact,
57    ArtifactFile,
58    DecoderContent,
59    SdkDefinition,
60    SdkExtension,
61    SdkOutputTree,
62    Compiler,
63    ProgramRelease,
64    LiveSpec,
65    StackManifest,
66    DeploymentRelease,
67    DecoderFixtureSet,
68    KnowledgeDocument,
69    KnowledgeSnapshot,
70    ExtensionSurface,
71    SdkInstallTarget,
72    CatalogBundle,
73    CatalogPublicationSet,
74}
75
76impl HashKindName {
77    pub const fn as_str(self) -> &'static str {
78        match self {
79            Self::IdlSource => "idl-source",
80            Self::IdlContent => "idl-content",
81            Self::IdlPortable => "idl-portable",
82            Self::IdlNormalized => "idl-normalized",
83            Self::ProgramSpec => "program-spec",
84            Self::AstPortable => "ast-portable",
85            Self::RuntimeArtifact => "runtime-artifact",
86            Self::ArtifactFile => "artifact-file",
87            Self::DecoderContent => "decoder-content",
88            Self::SdkDefinition => "sdk-definition",
89            Self::SdkExtension => "sdk-extension",
90            Self::SdkOutputTree => "sdk-output-tree",
91            Self::Compiler => "compiler",
92            Self::ProgramRelease => "program-release",
93            Self::LiveSpec => "live-spec",
94            Self::StackManifest => "stack-manifest",
95            Self::DeploymentRelease => "deployment-release",
96            Self::DecoderFixtureSet => "decoder-fixture-set",
97            Self::KnowledgeDocument => "knowledge-document",
98            Self::KnowledgeSnapshot => "knowledge-snapshot",
99            Self::ExtensionSurface => "extension-surface",
100            Self::SdkInstallTarget => "sdk-install-target",
101            Self::CatalogBundle => "catalog-bundle",
102            Self::CatalogPublicationSet => "catalog-publication-set",
103        }
104    }
105}
106
107impl fmt::Display for HashKindName {
108    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
109        formatter.write_str(self.as_str())
110    }
111}
112
113impl FromStr for HashKindName {
114    type Err = HashError;
115
116    fn from_str(value: &str) -> Result<Self, Self::Err> {
117        match value {
118            "idl-source" => Ok(Self::IdlSource),
119            "idl-content" => Ok(Self::IdlContent),
120            "idl-portable" => Ok(Self::IdlPortable),
121            "idl-normalized" => Ok(Self::IdlNormalized),
122            "program-spec" => Ok(Self::ProgramSpec),
123            "ast-portable" => Ok(Self::AstPortable),
124            "runtime-artifact" => Ok(Self::RuntimeArtifact),
125            "artifact-file" => Ok(Self::ArtifactFile),
126            "decoder-content" => Ok(Self::DecoderContent),
127            "sdk-definition" => Ok(Self::SdkDefinition),
128            "sdk-extension" => Ok(Self::SdkExtension),
129            "sdk-output-tree" => Ok(Self::SdkOutputTree),
130            "compiler" => Ok(Self::Compiler),
131            "program-release" => Ok(Self::ProgramRelease),
132            "live-spec" => Ok(Self::LiveSpec),
133            "stack-manifest" => Ok(Self::StackManifest),
134            "deployment-release" => Ok(Self::DeploymentRelease),
135            "decoder-fixture-set" => Ok(Self::DecoderFixtureSet),
136            "knowledge-document" => Ok(Self::KnowledgeDocument),
137            "knowledge-snapshot" => Ok(Self::KnowledgeSnapshot),
138            "extension-surface" => Ok(Self::ExtensionSurface),
139            "sdk-install-target" => Ok(Self::SdkInstallTarget),
140            "catalog-bundle" => Ok(Self::CatalogBundle),
141            "catalog-publication-set" => Ok(Self::CatalogPublicationSet),
142            _ => Err(HashError::UnknownKind(value.to_string())),
143        }
144    }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "kebab-case")]
149pub enum Visibility {
150    Public,
151    AuthenticatedOwner,
152    InternalOnly,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(rename_all = "kebab-case")]
157pub enum IdentityClass {
158    ExactSource,
159    CanonicalContent,
160    PortableContent,
161    NormalizedContent,
162    Composite,
163    ArtifactTree,
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct IdentityMetadata {
169    pub kind: HashKindName,
170    pub profile: CanonicalizationProfile,
171    pub visibility: Visibility,
172    pub identity_class: IdentityClass,
173    pub api_field: &'static str,
174    pub rust_type: &'static str,
175    pub typescript_type: &'static str,
176    pub projection: &'static str,
177    pub allowed_dto_audiences: &'static [Visibility],
178    pub database_mappings: &'static [&'static str],
179    pub legacy_aliases: &'static [&'static str],
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
183#[serde(rename_all = "camelCase")]
184pub struct NonHashIdentityMetadata {
185    pub api_field: &'static str,
186    pub rust_type: &'static str,
187    pub typescript_type: &'static str,
188    pub projection: &'static str,
189    pub visibility: Visibility,
190    pub allowed_dto_audiences: &'static [Visibility],
191    pub database_mappings: &'static [&'static str],
192    pub legacy_aliases: &'static [&'static str],
193}
194
195const PUBLIC_DTO_AUDIENCES: &[Visibility] = &[
196    Visibility::Public,
197    Visibility::AuthenticatedOwner,
198    Visibility::InternalOnly,
199];
200const OWNER_DTO_AUDIENCES: &[Visibility] =
201    &[Visibility::AuthenticatedOwner, Visibility::InternalOnly];
202const INTERNAL_DTO_AUDIENCES: &[Visibility] = &[Visibility::InternalOnly];
203
204const fn allowed_dto_audiences(visibility: Visibility) -> &'static [Visibility] {
205    match visibility {
206        Visibility::Public => PUBLIC_DTO_AUDIENCES,
207        Visibility::AuthenticatedOwner => OWNER_DTO_AUDIENCES,
208        Visibility::InternalOnly => INTERNAL_DTO_AUDIENCES,
209    }
210}
211
212const fn api_field(kind: HashKindName) -> &'static str {
213    match kind {
214        HashKindName::IdlSource => "sourceIdlHash",
215        HashKindName::IdlContent => "idlContentHash",
216        HashKindName::IdlPortable => "portableIdlHash",
217        HashKindName::IdlNormalized => "normalizedIdlHash",
218        HashKindName::ProgramSpec => "programSpecHash",
219        HashKindName::AstPortable => "portableAstHash",
220        HashKindName::RuntimeArtifact => "runtimeArtifactHash",
221        HashKindName::ArtifactFile => "artifactFileHash",
222        HashKindName::DecoderContent => "decoderContentHash",
223        HashKindName::SdkDefinition => "sdkDefinitionHash",
224        HashKindName::SdkExtension => "sdkExtensionHash",
225        HashKindName::SdkOutputTree => "sdkOutputTreeHash",
226        HashKindName::Compiler => "compilerHash",
227        HashKindName::ProgramRelease => "programReleaseHash",
228        HashKindName::LiveSpec => "liveSpecHash",
229        HashKindName::StackManifest => "stackManifestHash",
230        HashKindName::DeploymentRelease => "deploymentReleaseHash",
231        HashKindName::DecoderFixtureSet => "decoderFixtureSetHash",
232        HashKindName::KnowledgeDocument => "documentHash",
233        HashKindName::KnowledgeSnapshot => "knowledgeSnapshotHash",
234        HashKindName::ExtensionSurface => "surfaceHash",
235        HashKindName::SdkInstallTarget => "sdkInstallTargetHash",
236        HashKindName::CatalogBundle => "bundleHash",
237        HashKindName::CatalogPublicationSet => "setHash",
238    }
239}
240
241const fn rust_type(kind: HashKindName) -> &'static str {
242    match kind {
243        HashKindName::IdlSource => "HashId<IdlSource>",
244        HashKindName::IdlContent => "HashId<IdlContent>",
245        HashKindName::IdlPortable => "HashId<IdlPortable>",
246        HashKindName::IdlNormalized => "HashId<IdlNormalized>",
247        HashKindName::ProgramSpec => "HashId<ProgramSpec>",
248        HashKindName::AstPortable => "HashId<AstPortable>",
249        HashKindName::RuntimeArtifact => "HashId<RuntimeArtifact>",
250        HashKindName::ArtifactFile => "HashId<ArtifactFile>",
251        HashKindName::DecoderContent => "HashId<DecoderContent>",
252        HashKindName::SdkDefinition => "HashId<SdkDefinition>",
253        HashKindName::SdkExtension => "HashId<SdkExtension>",
254        HashKindName::SdkOutputTree => "HashId<SdkOutputTree>",
255        HashKindName::Compiler => "HashId<Compiler>",
256        HashKindName::ProgramRelease => "HashId<ProgramRelease>",
257        HashKindName::LiveSpec => "HashId<LiveSpec>",
258        HashKindName::StackManifest => "HashId<StackManifest>",
259        HashKindName::DeploymentRelease => "HashId<DeploymentRelease>",
260        HashKindName::DecoderFixtureSet => "HashId<DecoderFixtureSet>",
261        HashKindName::KnowledgeDocument => "HashId<KnowledgeDocument>",
262        HashKindName::KnowledgeSnapshot => "HashId<KnowledgeSnapshot>",
263        HashKindName::ExtensionSurface => "HashId<ExtensionSurface>",
264        HashKindName::SdkInstallTarget => "HashId<SdkInstallTarget>",
265        HashKindName::CatalogBundle => "HashId<CatalogBundle>",
266        HashKindName::CatalogPublicationSet => "HashId<CatalogPublicationSet>",
267    }
268}
269
270const fn typescript_type(kind: HashKindName) -> &'static str {
271    match kind {
272        HashKindName::IdlSource => "IdlSourceHash",
273        HashKindName::IdlContent => "IdlContentHash",
274        HashKindName::IdlPortable => "IdlPortableHash",
275        HashKindName::IdlNormalized => "IdlNormalizedHash",
276        HashKindName::ProgramSpec => "ProgramSpecHash",
277        HashKindName::AstPortable => "AstPortableHash",
278        HashKindName::RuntimeArtifact => "RuntimeArtifactHash",
279        HashKindName::ArtifactFile => "ArtifactFileHash",
280        HashKindName::DecoderContent => "DecoderContentHash",
281        HashKindName::SdkDefinition => "SdkDefinitionHash",
282        HashKindName::SdkExtension => "SdkExtensionHash",
283        HashKindName::SdkOutputTree => "SdkOutputTreeHash",
284        HashKindName::Compiler => "CompilerHash",
285        HashKindName::ProgramRelease => "ProgramReleaseHash",
286        HashKindName::LiveSpec => "LiveSpecHash",
287        HashKindName::StackManifest => "StackManifestHash",
288        HashKindName::DeploymentRelease => "DeploymentReleaseHash",
289        HashKindName::DecoderFixtureSet => "DecoderFixtureSetHash",
290        HashKindName::KnowledgeDocument => "KnowledgeDocumentHash",
291        HashKindName::KnowledgeSnapshot => "KnowledgeSnapshotHash",
292        HashKindName::ExtensionSurface => "ExtensionSurfaceHash",
293        HashKindName::SdkInstallTarget => "SdkInstallTargetHash",
294        HashKindName::CatalogBundle => "CatalogBundleHash",
295        HashKindName::CatalogPublicationSet => "CatalogPublicationSetHash",
296    }
297}
298
299const fn projection(kind: HashKindName) -> &'static str {
300    match kind {
301        HashKindName::IdlSource => "arete.idl-source/exact-bytes-v1",
302        HashKindName::IdlContent => "arete.idl-content/source-json-v1",
303        HashKindName::IdlPortable => "arete.idl-portable/source-json-v1",
304        HashKindName::IdlNormalized => "arete.idl-normalized/v1",
305        HashKindName::ProgramSpec => "arete.program-spec/v1",
306        HashKindName::AstPortable => "arete.ast-portable/self-hash-v1",
307        HashKindName::RuntimeArtifact => "arete.runtime-artifact/v1",
308        HashKindName::ArtifactFile => "arete.artifact-file/exact-bytes-v1",
309        HashKindName::DecoderContent => "arete.decoder-content/exact-bytes-v1",
310        HashKindName::SdkDefinition => "arete.sdk-definition/v1",
311        HashKindName::SdkExtension => "arete.sdk-extension/v1",
312        HashKindName::SdkOutputTree => "arete.sdk-output-tree/artifact-tree-v1",
313        HashKindName::Compiler => "arete.compiler/v1",
314        HashKindName::ProgramRelease => "arete.program-release/v1",
315        HashKindName::LiveSpec => "arete.artifact-envelope/live-spec-v1",
316        HashKindName::StackManifest => "arete.artifact-envelope/stack-manifest-v1",
317        HashKindName::DeploymentRelease => "arete.deployment-release/v1",
318        HashKindName::DecoderFixtureSet => "arete.decoder-fixtures/v2",
319        HashKindName::KnowledgeDocument => "arete.knowledge-document/v1",
320        HashKindName::KnowledgeSnapshot => "arete.knowledge-snapshot/v1",
321        HashKindName::ExtensionSurface => "arete.extension-surface/v2",
322        HashKindName::SdkInstallTarget => "arete.sdk-install-target/v1",
323        HashKindName::CatalogBundle => "arete.catalog-bundle/v1",
324        HashKindName::CatalogPublicationSet => "arete.catalog-publication-set/v1",
325    }
326}
327
328const fn database_mappings(kind: HashKindName) -> &'static [&'static str] {
329    match kind {
330        HashKindName::IdlSource | HashKindName::ArtifactFile => &[],
331        HashKindName::IdlContent => &[
332            "idl_contents.idl_content_hash",
333            "program_releases.idl_content_hash",
334        ],
335        HashKindName::IdlPortable => &["idl_contents.idl_portable_hash"],
336        HashKindName::IdlNormalized => &[
337            "idl_contents.idl_normalized_hash",
338            "decoder_bindings.normalized_idl_hash",
339            "program_releases.normalized_idl_hash",
340            "decoder_fixture_sets.normalized_idl_hash",
341        ],
342        HashKindName::ProgramSpec => &[
343            "idl_contents.program_spec_hash",
344            "program_spec_artifacts.program_spec_hash",
345            "program_releases.program_spec_hash",
346        ],
347        HashKindName::AstPortable => &[
348            "ast_contents.ast_portable_hash",
349            "builds.ast_portable_hash",
350            "deployments.current_ast_portable_hash",
351        ],
352        HashKindName::RuntimeArtifact => &[
353            "runtime_artifacts.runtime_artifact_hash",
354            "builds.runtime_artifact_hash",
355        ],
356        HashKindName::DecoderContent => &[
357            "decoder_contents.content_hash",
358            "decoder_executions.decoder_content_hash",
359        ],
360        HashKindName::SdkDefinition | HashKindName::Compiler => &[],
361        HashKindName::SdkExtension => &["sdk_extension_contents.sdk_extension_hash"],
362        HashKindName::SdkOutputTree => &["sdk_extension_contents.sdk_output_tree_hash"],
363        HashKindName::ProgramRelease => &["program_releases.release_hash"],
364        HashKindName::LiveSpec => &["live_spec_artifacts.live_spec_hash"],
365        HashKindName::StackManifest => &["stack_manifest_artifacts.stack_manifest_hash"],
366        HashKindName::DeploymentRelease => &[
367            "deployment_releases.deployment_release_hash",
368            "builds.deployment_release_hash",
369            "deployments.deployment_release_hash",
370        ],
371        HashKindName::DecoderFixtureSet => &["decoder_fixture_sets.fixture_set_hash"],
372        HashKindName::KnowledgeDocument => &[
373            "knowledge_document_artifacts.document_hash",
374            "knowledge_snapshot_documents.document_hash",
375            "catalog_bundle_documents.document_hash",
376        ],
377        HashKindName::KnowledgeSnapshot => &[
378            "knowledge_snapshot_artifacts.snapshot_hash",
379            "catalog_publication_sets.knowledge_snapshot_hash",
380        ],
381        HashKindName::ExtensionSurface => &[
382            "extension_surface_artifacts_v2.surface_hash",
383            "sdk_install_target_artifacts.surface_hash",
384            "catalog_bundle_surfaces.surface_hash",
385        ],
386        HashKindName::SdkInstallTarget => &[
387            "sdk_install_target_artifacts.sdk_install_target_hash",
388            "registry_package_sdk_targets.sdk_install_target_hash",
389            "catalog_bundle_sdk_targets.sdk_install_target_hash",
390        ],
391        HashKindName::CatalogBundle => &[
392            "catalog_bundles.bundle_hash",
393            "catalog_publication_set_entries.bundle_hash",
394        ],
395        HashKindName::CatalogPublicationSet => &[
396            "catalog_publication_sets.set_hash",
397            "catalog_active_sets.set_hash",
398            "catalog_publication_events.set_hash",
399        ],
400    }
401}
402
403const fn legacy_aliases(kind: HashKindName) -> &'static [&'static str] {
404    match kind {
405        HashKindName::IdlContent => &["legacy_idl_json_sha256"],
406        HashKindName::IdlPortable => &["legacy_idl_json_no_program_sha256"],
407        HashKindName::IdlNormalized => &["legacy_normalized_idl_sha256"],
408        HashKindName::AstPortable => &["legacy_portable_ast_sha256"],
409        HashKindName::RuntimeArtifact => &["legacy_platform_ast_sha256"],
410        HashKindName::DecoderContent => &["legacy_decoder_content_sha256"],
411        HashKindName::SdkExtension => &["legacy_sdk_extension_sha256"],
412        _ => &[],
413    }
414}
415
416mod sealed {
417    pub trait Sealed {}
418}
419
420pub trait Kind: sealed::Sealed + 'static {
421    const NAME: HashKindName;
422    const PROFILE: CanonicalizationProfile;
423    const VISIBILITY: Visibility;
424    const IDENTITY_CLASS: IdentityClass;
425}
426
427macro_rules! define_kinds {
428    ($(($type:ident, $name:ident, $profile:ident, $visibility:ident, $class:ident)),+ $(,)?) => {
429        $(
430            #[derive(Debug)]
431            pub struct $type;
432
433            impl sealed::Sealed for $type {}
434
435            impl Kind for $type {
436                const NAME: HashKindName = HashKindName::$name;
437                const PROFILE: CanonicalizationProfile = CanonicalizationProfile::$profile;
438                const VISIBILITY: Visibility = Visibility::$visibility;
439                const IDENTITY_CLASS: IdentityClass = IdentityClass::$class;
440            }
441        )+
442
443        pub const IDENTITY_REGISTRY: &[IdentityMetadata] = &[
444            $(IdentityMetadata {
445                kind: HashKindName::$name,
446                profile: CanonicalizationProfile::$profile,
447                visibility: Visibility::$visibility,
448                identity_class: IdentityClass::$class,
449                api_field: api_field(HashKindName::$name),
450                rust_type: rust_type(HashKindName::$name),
451                typescript_type: typescript_type(HashKindName::$name),
452                projection: projection(HashKindName::$name),
453                allowed_dto_audiences: allowed_dto_audiences(Visibility::$visibility),
454                database_mappings: database_mappings(HashKindName::$name),
455                legacy_aliases: legacy_aliases(HashKindName::$name),
456            }),+
457        ];
458    };
459}
460
461define_kinds!(
462    (IdlSource, IdlSource, RawBytesV1, Public, ExactSource),
463    (IdlContent, IdlContent, AreteJcsV1, Public, CanonicalContent),
464    (
465        IdlPortable,
466        IdlPortable,
467        AreteJcsV1,
468        Public,
469        PortableContent
470    ),
471    (
472        IdlNormalized,
473        IdlNormalized,
474        AreteJcsV1,
475        Public,
476        NormalizedContent
477    ),
478    (ProgramSpec, ProgramSpec, AreteJcsV1, Public, Composite),
479    (
480        AstPortable,
481        AstPortable,
482        AreteJcsV1,
483        Public,
484        PortableContent
485    ),
486    (
487        RuntimeArtifact,
488        RuntimeArtifact,
489        AreteJcsV1,
490        InternalOnly,
491        Composite
492    ),
493    (
494        ArtifactFile,
495        ArtifactFile,
496        RawBytesV1,
497        Public,
498        CanonicalContent
499    ),
500    (
501        DecoderContent,
502        DecoderContent,
503        RawBytesV1,
504        InternalOnly,
505        CanonicalContent
506    ),
507    (SdkDefinition, SdkDefinition, AreteJcsV1, Public, Composite),
508    (SdkExtension, SdkExtension, AreteJcsV1, Public, Composite),
509    (
510        SdkOutputTree,
511        SdkOutputTree,
512        ArtifactTreeV1,
513        Public,
514        ArtifactTree
515    ),
516    (Compiler, Compiler, FramedTupleV1, Public, Composite),
517    (
518        ProgramRelease,
519        ProgramRelease,
520        AreteJcsV1,
521        Public,
522        Composite
523    ),
524    (LiveSpec, LiveSpec, AreteJcsV1, Public, Composite),
525    (StackManifest, StackManifest, AreteJcsV1, Public, Composite),
526    (
527        DeploymentRelease,
528        DeploymentRelease,
529        AreteJcsV1,
530        AuthenticatedOwner,
531        Composite
532    ),
533    (
534        DecoderFixtureSet,
535        DecoderFixtureSet,
536        AreteJcsV1,
537        InternalOnly,
538        Composite
539    ),
540    (
541        KnowledgeDocument,
542        KnowledgeDocument,
543        AreteJcsV1,
544        Public,
545        CanonicalContent
546    ),
547    (
548        KnowledgeSnapshot,
549        KnowledgeSnapshot,
550        AreteJcsV1,
551        Public,
552        Composite
553    ),
554    (
555        ExtensionSurface,
556        ExtensionSurface,
557        AreteJcsV1,
558        Public,
559        Composite
560    ),
561    (
562        SdkInstallTarget,
563        SdkInstallTarget,
564        AreteJcsV1,
565        Public,
566        Composite
567    ),
568    (CatalogBundle, CatalogBundle, AreteJcsV1, Public, Composite),
569    (
570        CatalogPublicationSet,
571        CatalogPublicationSet,
572        AreteJcsV1,
573        Public,
574        Composite
575    ),
576);
577
578pub const NON_HASH_IDENTITY_REGISTRY: &[NonHashIdentityMetadata] = &[
579    NonHashIdentityMetadata {
580        api_field: "programReadBindingId",
581        rust_type: "ProgramReadBindingId",
582        typescript_type: "ProgramReadBindingId",
583        projection: "arete.program-read-binding/v1",
584        visibility: Visibility::Public,
585        allowed_dto_audiences: PUBLIC_DTO_AUDIENCES,
586        database_mappings: &[
587            "program_read_bindings.id",
588            "program_read_routes.program_read_binding_id",
589            "program_read_usage_events.program_read_binding_id",
590        ],
591        legacy_aliases: &[],
592    },
593    NonHashIdentityMetadata {
594        api_field: "decoderBindingId",
595        rust_type: "internal::DecoderBindingId",
596        typescript_type: "DecoderBindingId",
597        projection: "arete.decoder-binding/v1",
598        visibility: Visibility::InternalOnly,
599        allowed_dto_audiences: INTERNAL_DTO_AUDIENCES,
600        database_mappings: &["decoder_bindings.id", "program_releases.decoder_binding_id"],
601        legacy_aliases: &[],
602    },
603    NonHashIdentityMetadata {
604        api_field: "decoderEngineId",
605        rust_type: "internal::DecoderEngineId",
606        typescript_type: "DecoderEngineId",
607        projection: "arete.decoder-engine/v1",
608        visibility: Visibility::InternalOnly,
609        allowed_dto_audiences: INTERNAL_DTO_AUDIENCES,
610        database_mappings: &[
611            "decoder_executions.decoder_engine_id",
612            "program_releases.decoder_engine_id",
613            "decoder_fixture_sets.decoder_engine_id",
614        ],
615        legacy_aliases: &[],
616    },
617];
618
619pub fn identity_metadata(kind: HashKindName) -> &'static IdentityMetadata {
620    IDENTITY_REGISTRY
621        .iter()
622        .find(|metadata| metadata.kind == kind)
623        .expect("closed hash kind registry is exhaustive")
624}