package quartermaster import ( "encoding/json" "sort" "time" ) // SessionTransitArtifact is recorded when a Shellstream session crosses // the inter-cluster boundary (overlay mode). type SessionTransitArtifact struct { SessionID string `json:"session_id"` SATHash []byte `json:"sat_hash"` SourceCluster string `json:"source_cluster"` DestCluster string `json:"dest_cluster"` TargetDevice string `json:"target_device"` TargetSubnet string `json:"target_subnet"` GrantedMode string `json:"granted_mode"` // "overlay" | "underlay" | "both" GrantedOperations []string `json:"granted_operations"` OperationType string `json:"operation_type"` Timestamp time.Time `json:"timestamp"` CapabilityTokenHash []byte `json:"capability_token_hash"` } // ArtifactID returns the unique identifier for this artifact. func (a *SessionTransitArtifact) ArtifactID() string { return a.SessionID } // RegistryType returns the registry type discriminator. func (a *SessionTransitArtifact) RegistryType() string { return "session-transit" } // CanonicalBytes returns the RFC 8785 (JCS) canonical JSON serialization // for deterministic hashing and merkle anchoring. func (a *SessionTransitArtifact) CanonicalBytes() ([]byte, error) { // Sort granted operations for deterministic output. ops := make([]string, len(a.GrantedOperations)) copy(ops, a.GrantedOperations) sort.Strings(ops) canonical := map[string]any{ "session_id": a.SessionID, "sat_hash": a.SATHash, "source_cluster": a.SourceCluster, "dest_cluster": a.DestCluster, "target_device": a.TargetDevice, "target_subnet": a.TargetSubnet, "granted_mode": a.GrantedMode, "granted_operations": ops, "operation_type": a.OperationType, "timestamp": a.Timestamp.UTC().Format(time.RFC3339Nano), "capability_token_hash": a.CapabilityTokenHash, } // TODO: Use a proper JCS (RFC 8785) library for canonical serialization. // For now, json.Marshal with sorted keys provides deterministic output. return json.Marshal(canonical) }