catalogd: BiometricCollection on Go SubjectManifest reader

Mirrors lakehouse f1fa6e4 (Phase 1.6 Gate 3a). Adds:

  type BiometricCollection struct {
      DataPath           string
      TemplateHash       string
      CollectedAt        time.Time
      ConsentVersionHash string          (omitempty)
      Classifications    json.RawMessage (omitempty)
  }

SubjectManifest gains *BiometricCollection biometric_collection,omitempty
field so manifests written by the Rust biometric upload endpoint
round-trip through the Go reader without losing the new collection
record.

Cross-runtime parity probe (subject_audit_parity.sh) still 6/6
byte-identical after the field addition — the omitempty pointer means
manifests without a collection record produce identical bytes to
pre-Gate-3a manifests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-05-03 04:55:49 -05:00
parent 857ca4c971
commit df2a9d1f77

View File

@ -37,19 +37,35 @@ const GenesisHash = "GENESIS"
// SubjectManifest mirrors crates/shared/src/types.rs::SubjectManifest. // SubjectManifest mirrors crates/shared/src/types.rs::SubjectManifest.
// Keep field tags byte-identical so manifest JSON written by Rust round-trips. // Keep field tags byte-identical so manifest JSON written by Rust round-trips.
//
// Vertical: omitempty matches Rust's serde-default deserializer accepting
// missing field (default Unknown). For the OTHER fields we deliberately
// avoid omitempty since Rust always emits them.
type SubjectManifest struct { type SubjectManifest struct {
Schema string `json:"schema"` Schema string `json:"schema"`
CandidateID string `json:"candidate_id"` CandidateID string `json:"candidate_id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Status string `json:"status"` Status string `json:"status"`
Vertical string `json:"vertical,omitempty"` Vertical string `json:"vertical,omitempty"`
Consent SubjectConsent `json:"consent"` Consent SubjectConsent `json:"consent"`
Retention SubjectRetention `json:"retention"` Retention SubjectRetention `json:"retention"`
Datasets []SubjectDatasetRef `json:"datasets"` Datasets []SubjectDatasetRef `json:"datasets"`
SafeViews []string `json:"safe_views"` SafeViews []string `json:"safe_views"`
AuditLogPath string `json:"audit_log_path"` AuditLogPath string `json:"audit_log_path"`
AuditLogChainRoot string `json:"audit_log_chain_root"` AuditLogChainRoot string `json:"audit_log_chain_root"`
BiometricCollection *BiometricCollection `json:"biometric_collection,omitempty"`
}
// BiometricCollection mirrors crates/shared/src/types.rs::BiometricCollection.
// Written by the Phase 1.6 Gate 3 photo-upload endpoint. None until the
// first biometric upload; cleared on BIPA erasure.
type BiometricCollection struct {
DataPath string `json:"data_path"`
TemplateHash string `json:"template_hash"`
CollectedAt time.Time `json:"collected_at"`
ConsentVersionHash string `json:"consent_version_hash,omitempty"`
Classifications json.RawMessage `json:"classifications,omitempty"`
} }
type SubjectConsent struct { type SubjectConsent struct {