Accept Run SQLite schema probe source fingerprints

This commit is contained in:
npc0-hue
2026-08-12 22:20:33 +08:00
parent 2246859984
commit 3fe74399a9
10 changed files with 139 additions and 15 deletions
+1
View File
@@ -159,6 +159,7 @@ type SCUMSchemaProbeResult struct {
JobID string
Binding SCUMBindingIdentity
Status SCUMCapabilityEvidenceStatus
SourceFingerprint string
SchemaFingerprint string
ObservedAt time.Time
ResultDigest string
+2 -1
View File
@@ -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)
}
}
+3 -2
View File
@@ -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 {
+2 -2
View File
@@ -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)
}
}
+5 -1
View File
@@ -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))
}
}
+17 -1
View File
@@ -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) {