Accept Run SQLite schema probe source fingerprints
This commit is contained in:
@@ -159,6 +159,7 @@ type SCUMSchemaProbeResult struct {
|
||||
JobID string
|
||||
Binding SCUMBindingIdentity
|
||||
Status SCUMCapabilityEvidenceStatus
|
||||
SourceFingerprint string
|
||||
SchemaFingerprint string
|
||||
ObservedAt time.Time
|
||||
ResultDigest string
|
||||
|
||||
@@ -47,6 +47,7 @@ func TestRunJobResultRequestParsesSQLiteSchemaProbeEnvelope(t *testing.T) {
|
||||
"jobId":"job-probe",
|
||||
"binding":{"serverInstanceId":"server-scum","runBindingId":"runtime-binding-server-scum","runEndpointId":"run-local","pluginId":"server.scum","pluginVersion":"1.0.0","adapterVersion":"scum-live-data-v0","gameVersion":"1.0.0","databaseIdentity":"scum-database"},
|
||||
"status":"compatible",
|
||||
"sourceFingerprint":"sha256:` + strings.Repeat("c", 64) + `",
|
||||
"schemaFingerprint":"sha256:` + strings.Repeat("a", 64) + `",
|
||||
"observedAt":"2026-08-12T00:00:00Z",
|
||||
"resultDigest":"sha256:` + strings.Repeat("b", 64) + `",
|
||||
@@ -60,7 +61,7 @@ func TestRunJobResultRequestParsesSQLiteSchemaProbeEnvelope(t *testing.T) {
|
||||
}
|
||||
domainRequest := request.ToDomain()
|
||||
probe := domainRequest.ExecutionResult.SQLiteSchemaProbe
|
||||
if probe == nil || probe.JobID != "job-probe" || probe.Binding.DatabaseIdentity != "scum-database" || probe.ResultDigest != "sha256:"+strings.Repeat("b", 64) {
|
||||
if probe == nil || probe.JobID != "job-probe" || probe.Binding.DatabaseIdentity != "scum-database" || probe.SourceFingerprint != "sha256:"+strings.Repeat("c", 64) || probe.ResultDigest != "sha256:"+strings.Repeat("b", 64) {
|
||||
t.Fatalf("sqliteSchemaProbe envelope did not parse: %+v", probe)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ type SCUMSchemaProbeResultDTO struct {
|
||||
JobID string `json:"jobId"`
|
||||
Binding SCUMBindingIdentityDTO `json:"binding"`
|
||||
Status string `json:"status"`
|
||||
SourceFingerprint string `json:"sourceFingerprint,omitempty"`
|
||||
SchemaFingerprint string `json:"schemaFingerprint,omitempty"`
|
||||
ObservedAt time.Time `json:"observedAt"`
|
||||
ResultDigest string `json:"resultDigest,omitempty"`
|
||||
@@ -147,7 +148,7 @@ func SCUMSchemaProbeResultToDomain(value SCUMSchemaProbeResultDTO) domain.SCUMSc
|
||||
}
|
||||
objects[index] = domain.SCUMSchemaObjectEvidence{ObjectHash: object.ObjectHash, Kind: object.Kind, NameFingerprint: object.NameFingerprint, DeclaredColumns: columns, Indexes: indexes, ForeignKeys: foreignKeys, ApproximateRows: object.ApproximateRows, SampleFingerprints: append([]string(nil), object.SampleFingerprints...)}
|
||||
}
|
||||
return domain.SCUMSchemaProbeResult{RequestID: value.RequestID, JobID: value.JobID, Binding: scumBindingIdentityToDomain(value.Binding), Status: domain.SCUMCapabilityEvidenceStatus(value.Status), SchemaFingerprint: value.SchemaFingerprint, ObservedAt: value.ObservedAt, ResultDigest: value.ResultDigest, Objects: objects, SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorCode(value.SafeError.Code), Message: value.SafeError.Message, Retryable: value.SafeError.Retryable}, Limits: scumProbeBoundsToDomain(value.Limits)}
|
||||
return domain.SCUMSchemaProbeResult{RequestID: value.RequestID, JobID: value.JobID, Binding: scumBindingIdentityToDomain(value.Binding), Status: domain.SCUMCapabilityEvidenceStatus(value.Status), SourceFingerprint: value.SourceFingerprint, SchemaFingerprint: value.SchemaFingerprint, ObservedAt: value.ObservedAt, ResultDigest: value.ResultDigest, Objects: objects, SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorCode(value.SafeError.Code), Message: value.SafeError.Message, Retryable: value.SafeError.Retryable}, Limits: scumProbeBoundsToDomain(value.Limits)}
|
||||
}
|
||||
|
||||
func SCUMSchemaProbeResultPtrToDomain(value *SCUMSchemaProbeResultDTO) *domain.SCUMSchemaProbeResult {
|
||||
@@ -176,7 +177,7 @@ func SCUMSchemaProbeResultFromDomain(value domain.SCUMSchemaProbeResult) SCUMSch
|
||||
}
|
||||
objects[index] = SCUMSchemaObjectEvidenceDTO{ObjectHash: object.ObjectHash, Kind: object.Kind, NameFingerprint: object.NameFingerprint, DeclaredColumns: columns, Indexes: indexes, ForeignKeys: foreignKeys, ApproximateRows: object.ApproximateRows, SampleFingerprints: append([]string(nil), object.SampleFingerprints...)}
|
||||
}
|
||||
return SCUMSchemaProbeResultDTO{RequestID: value.RequestID, JobID: value.JobID, Binding: scumBindingIdentityFromDomain(value.Binding), Status: string(value.Status), SchemaFingerprint: value.SchemaFingerprint, ObservedAt: value.ObservedAt, ResultDigest: value.ResultDigest, Objects: objects, SafeError: SCUMSafeErrorDTO{Code: string(value.SafeError.Code), Message: value.SafeError.Message, Retryable: value.SafeError.Retryable}, Limits: scumProbeBoundsFromDomain(value.Limits)}
|
||||
return SCUMSchemaProbeResultDTO{RequestID: value.RequestID, JobID: value.JobID, Binding: scumBindingIdentityFromDomain(value.Binding), Status: string(value.Status), SourceFingerprint: value.SourceFingerprint, SchemaFingerprint: value.SchemaFingerprint, ObservedAt: value.ObservedAt, ResultDigest: value.ResultDigest, Objects: objects, SafeError: SCUMSafeErrorDTO{Code: string(value.SafeError.Code), Message: value.SafeError.Message, Retryable: value.SafeError.Retryable}, Limits: scumProbeBoundsFromDomain(value.Limits)}
|
||||
}
|
||||
|
||||
func SCUMSchemaProbeResultPtrFromDomain(value *domain.SCUMSchemaProbeResult) *SCUMSchemaProbeResultDTO {
|
||||
|
||||
@@ -191,7 +191,7 @@ func TestSCUMSchemaProbeDispatchIsPlatformScheduledAndFenced(t *testing.T) {
|
||||
}
|
||||
badBinding := probeRequest.Binding
|
||||
badBinding.RunBindingID = "runtime-binding-other"
|
||||
badProbe := domain.SCUMSchemaProbeResult{RequestID: probeRequest.RequestID, JobID: probeRequest.JobID, Binding: badBinding, Status: domain.SCUMCapabilityEvidenceCompatible, SchemaFingerprint: "sha256:" + strings.Repeat("a", 64), ObservedAt: fixedTime, ResultDigest: "sha256:" + strings.Repeat("b", 64), Limits: probeRequest.Bounds}
|
||||
badProbe := domain.SCUMSchemaProbeResult{RequestID: probeRequest.RequestID, JobID: probeRequest.JobID, Binding: badBinding, Status: domain.SCUMCapabilityEvidenceCompatible, SourceFingerprint: "sha256:" + strings.Repeat("c", 64), SchemaFingerprint: "sha256:" + strings.Repeat("a", 64), ObservedAt: fixedTime, ResultDigest: "sha256:" + strings.Repeat("b", 64), Limits: probeRequest.Bounds}
|
||||
_, err = svc.CompleteRunJob(domain.RunJobResult{RunEndpointID: endpoint.ID, SessionToken: hello.SessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, State: domain.JobStateSucceeded, Progress: domain.RunJobProgressReport{Percent: 100, Message: "probe complete"}, Message: "probe complete", ExecutionResult: domain.JobExecutionResult{Kind: scumSchemaProbeExecutionKind, SQLiteSchemaProbe: &badProbe, AuditSummary: "redacted schema probe"}})
|
||||
if err == nil || !strings.Contains(err.Error(), "binding identity") {
|
||||
t.Fatalf("expected binding mismatch rejection, got %v", err)
|
||||
@@ -205,7 +205,7 @@ func TestSCUMSchemaProbeDispatchIsPlatformScheduledAndFenced(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("get completed probe job: %v", err)
|
||||
}
|
||||
if stored.ExecutionResult.SQLiteSchemaProbe == nil || stored.ExecutionResult.SQLiteSchemaProbe.ResultDigest != goodProbe.ResultDigest || stored.ExecutionResult.SQLiteSchemaProbe.Binding.RunBindingID != probeRequest.Binding.RunBindingID {
|
||||
if stored.ExecutionResult.SQLiteSchemaProbe == nil || stored.ExecutionResult.SQLiteSchemaProbe.SourceFingerprint != goodProbe.SourceFingerprint || stored.ExecutionResult.SQLiteSchemaProbe.ResultDigest != goodProbe.ResultDigest || stored.ExecutionResult.SQLiteSchemaProbe.Binding.RunBindingID != probeRequest.Binding.RunBindingID {
|
||||
t.Fatalf("typed probe result was not persisted safely: %+v", stored.ExecutionResult.SQLiteSchemaProbe)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
)
|
||||
|
||||
var scumHashPattern = regexp.MustCompile(`^sha256:[a-fA-F0-9]{64}$|^[a-fA-F0-9]{16,128}$`)
|
||||
var scumDataTargetSafeErrorPattern = regexp.MustCompile(`^data_target_[a-z0-9_]{1,80}$`)
|
||||
|
||||
func ValidateSCUMSchemaProbeRequest(request domain.SCUMSchemaProbeRequest) error {
|
||||
var violations []string
|
||||
@@ -37,6 +38,9 @@ func ValidateSCUMSchemaProbeResult(result domain.SCUMSchemaProbeResult) error {
|
||||
if !validSCUMSchemaProbeResultStatus(result.Status) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) {
|
||||
violations = append(violations, "sourceFingerprint must be a digest/fingerprint")
|
||||
}
|
||||
if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) {
|
||||
violations = append(violations, "schemaFingerprint must be a digest/fingerprint")
|
||||
}
|
||||
@@ -302,7 +306,7 @@ func validSCUMSafeErrorCode(value domain.SCUMSafeErrorCode) bool {
|
||||
case "", domain.SCUMSafeErrorNone, domain.SCUMSafeErrorProbeExecutorAbsent, domain.SCUMSafeErrorProbeMissing, domain.SCUMSafeErrorProbeFailed, domain.SCUMSafeErrorSchemaIncompatible, domain.SCUMSafeErrorBindingMismatch, domain.SCUMSafeErrorAdapterMismatch, domain.SCUMSafeErrorFingerprintMismatch, domain.SCUMSafeErrorDigestMismatch, domain.SCUMSafeErrorEvidenceExpired, domain.SCUMSafeErrorInvalidProbePayload, domain.SCUMSafeErrorInvalidRequest, domain.SCUMSafeErrorTargetUnavailable, domain.SCUMSafeErrorSourceUnavailable, domain.SCUMSafeErrorSQLiteOpenFailed, domain.SCUMSafeErrorSQLiteReadFailed, domain.SCUMSafeErrorDatabaseBusy, domain.SCUMSafeErrorTimeout, domain.SCUMSafeErrorCancelled, domain.SCUMSafeErrorSourceChanged, domain.SCUMSafeErrorResultLimitExceeded:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
return scumDataTargetSafeErrorPattern.MatchString(string(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,11 +55,17 @@ func TestValidateSCUMSchemaProbeResultRejectsRawSQLAndRows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateSCUMSchemaProbeResultAcceptsRunTerminalStatuses(t *testing.T) {
|
||||
succeeded := domain.SCUMSchemaProbeResult{RequestID: "probe-1", JobID: "job-1", Binding: validatorSCUMBinding(), Status: domain.SCUMSchemaProbeStatusSucceeded, SchemaFingerprint: scumProbeHash, ObservedAt: time.Now(), ResultDigest: scumProbeHash, Limits: domain.DefaultSCUMSchemaProbeBounds(), SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}}
|
||||
succeeded := domain.SCUMSchemaProbeResult{RequestID: "probe-1", JobID: "job-1", Binding: validatorSCUMBinding(), Status: domain.SCUMSchemaProbeStatusSucceeded, SourceFingerprint: scumProbeHash, SchemaFingerprint: scumProbeHash, ObservedAt: time.Now(), ResultDigest: scumProbeHash, Limits: domain.DefaultSCUMSchemaProbeBounds(), SafeError: domain.SCUMSafeError{Code: domain.SCUMSafeErrorNone}}
|
||||
if err := ValidateSCUMSchemaProbeResult(succeeded); err != nil {
|
||||
t.Fatalf("expected succeeded Run probe result to validate, got %v", err)
|
||||
}
|
||||
|
||||
succeeded.SourceFingerprint = "C:/db/SCUM.db"
|
||||
if err := ValidateSCUMSchemaProbeResult(succeeded); err == nil || !strings.Contains(err.Error(), "sourceFingerprint must be a digest/fingerprint") {
|
||||
t.Fatalf("expected raw source fingerprint rejection, got %v", err)
|
||||
}
|
||||
succeeded.SourceFingerprint = scumProbeHash
|
||||
|
||||
failed := succeeded
|
||||
failed.Status = domain.SCUMCapabilityEvidenceFailed
|
||||
failed.SchemaFingerprint = ""
|
||||
@@ -67,6 +73,16 @@ func TestValidateSCUMSchemaProbeResultAcceptsRunTerminalStatuses(t *testing.T) {
|
||||
if err := ValidateSCUMSchemaProbeResult(failed); err != nil {
|
||||
t.Fatalf("expected safe failed Run probe result to validate, got %v", err)
|
||||
}
|
||||
|
||||
failed.SafeError = domain.SCUMSafeError{Code: domain.SCUMSafeErrorCode("data_target_plan_invalid"), Retryable: false}
|
||||
if err := ValidateSCUMSchemaProbeResult(failed); err != nil {
|
||||
t.Fatalf("expected generic Run data-target failure to validate, got %v", err)
|
||||
}
|
||||
|
||||
failed.SafeError = domain.SCUMSafeError{Code: domain.SCUMSafeErrorCode("data_target_invalid path"), Retryable: false}
|
||||
if err := ValidateSCUMSchemaProbeResult(failed); err == nil || !strings.Contains(err.Error(), "safeError.code is invalid") {
|
||||
t.Fatalf("expected unsafe data-target code rejection, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSCUMCapabilityEvidenceRequiresSafeCurrentServiceIdentity(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user