package projectlog import ( "encoding/json" "errors" "strings" "testing" "time" "iop/packages/go/agentpolicy" "iop/packages/go/agentprovider/cli/status" "iop/packages/go/agenttask" ) var recordTestNow = time.Date(2026, 7, 29, 12, 0, 0, 0, time.UTC) func validQuotaObservation() agentpolicy.QuotaObservation { snapshot := status.NormalizeQuotaSnapshot( "prov-1", "prof-1", nil, recordTestNow, nil, nil, ) return agentpolicy.NormalizeQuotaObservation(snapshot, recordTestNow, time.Minute) } func validRecord() ProjectLogRecord { quota := validQuotaObservation() return ProjectLogRecord{ SchemaVersion: RecordSchemaVersion, RecordID: "rec-001", Sequence: 1, LoopOrdinal: 1, DispatchOrdinal: 1, ProjectID: agenttask.ProjectID("proj-001"), WorkspaceID: agenttask.WorkspaceID("ws-001"), WorkUnitID: agenttask.WorkUnitID("work-001"), AttemptID: agenttask.AttemptID("att-001"), CommandID: agenttask.CommandID("cmd-001"), EventType: agenttask.EventManualStart, State: agenttask.WorkStateDispatching, StateRevision: agenttask.StateRevision("rev-1"), RouteStatus: &RouteStatus{ ProviderID: "prov-1", ProfileID: "prof-1", ModelID: "model-1", RuleID: "rule-1", ProfileRevision: "profile-rev-1", ConfigRevision: "config-rev-1", SelectionRevision: "selection-rev-1", ReasonCode: "matched", }, QuotaObservation: "a, Locators: []agenttask.LocatorRecord{ { Kind: agenttask.LocatorSession, Opaque: "sess-12345", Revision: "locator-rev-1", ProjectID: agenttask.ProjectID("proj-001"), WorkspaceID: agenttask.WorkspaceID("ws-001"), WorkUnitID: agenttask.WorkUnitID("work-001"), AttemptID: agenttask.AttemptID("att-001"), }, }, BlockerCode: "", Message: "Task started successfully", Metadata: map[string]string{ "step": "initialization", }, Timestamp: recordTestNow, Terminal: false, } } func TestRecordValidationMatrix(t *testing.T) { t.Run("valid record passes", func(t *testing.T) { rec := validRecord() if err := rec.Validate(); err != nil { t.Fatalf("expected valid record to pass, got: %v", err) } }) t.Run("invalid schema version", func(t *testing.T) { rec := validRecord() rec.SchemaVersion = 99 if err := rec.Validate(); !errors.Is(err, ErrInvalidSchemaVersion) { t.Fatalf("expected ErrInvalidSchemaVersion, got: %v", err) } }) t.Run("required record identity", func(t *testing.T) { rec := validRecord() rec.RecordID = "" if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected ErrInvalidIdentity for empty RecordID, got: %v", err) } }) t.Run("known event state and blocker enums", func(t *testing.T) { tests := []struct { name string mutate func(*ProjectLogRecord) }{ {"empty event", func(rec *ProjectLogRecord) { rec.EventType = "" }}, {"unknown event", func(rec *ProjectLogRecord) { rec.EventType = "future_event" }}, {"unknown state", func(rec *ProjectLogRecord) { rec.State = "future_state" }}, {"revision without state", func(rec *ProjectLogRecord) { rec.State = "" }}, {"unknown blocker", func(rec *ProjectLogRecord) { rec.BlockerCode = "future_blocker" }}, {"terminal mismatch", func(rec *ProjectLogRecord) { rec.Terminal = true }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { rec := validRecord() test.mutate(&rec) if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected ErrInvalidIdentity, got: %v", err) } }) } }) t.Run("state revision is optional when exact evidence is unavailable", func(t *testing.T) { rec := validRecord() rec.StateRevision = "" if err := rec.Validate(); err != nil { t.Fatalf("record with state and no revision rejected: %v", err) } }) t.Run("route identities and reason code are strict", func(t *testing.T) { tests := []struct { name string mutate func(*RouteStatus) }{ {"provider required", func(route *RouteStatus) { route.ProviderID = "" }}, {"model bounded", func(route *RouteStatus) { route.ModelID = strings.Repeat("m", MaxIDLength+1) }}, {"profile revision required", func(route *RouteStatus) { route.ProfileRevision = "" }}, {"config revision required", func(route *RouteStatus) { route.ConfigRevision = "" }}, {"reason lowercase", func(route *RouteStatus) { route.ReasonCode = "Matched Route" }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { rec := validRecord() test.mutate(rec.RouteStatus) if err := rec.Validate(); err == nil { t.Fatal("expected route validation error") } }) } }) t.Run("quota observation accepts sealed and canonical corrupt values", func(t *testing.T) { rec := validRecord() corrupt := agentpolicy.CorruptQuotaObservation() rec.QuotaObservation = &corrupt if err := rec.Validate(); err != nil { t.Fatalf("canonical corrupt quota observation rejected: %v", err) } rec = validRecord() invalid := agentpolicy.QuotaObservation{Validity: agentpolicy.ObservationValid} rec.QuotaObservation = &invalid if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected invalid unsealed quota rejection, got: %v", err) } rec = validRecord() oversizedSnapshot := status.NormalizeQuotaSnapshot( strings.Repeat("a", MaxIDLength+1), "prof-1", nil, recordTestNow, nil, nil, ) oversizedQuota := agentpolicy.NormalizeQuotaObservation(oversizedSnapshot, recordTestNow, time.Minute) rec.QuotaObservation = &oversizedQuota if err := rec.Validate(); !errors.Is(err, ErrUnboundedField) { t.Fatalf("expected ErrUnboundedField for sealed oversized adapter, got: %v", err) } rec = validRecord() sensitiveSnapshot := status.NormalizeQuotaSnapshot( "prov-1", "Bearer provider-secret", nil, recordTestNow, nil, nil, ) sensitiveQuota := agentpolicy.NormalizeQuotaObservation(sensitiveSnapshot, recordTestNow, time.Minute) rec.QuotaObservation = &sensitiveQuota if err := rec.Validate(); !errors.Is(err, ErrSensitiveContent) { t.Fatalf("expected ErrSensitiveContent for sealed sensitive target, got: %v", err) } }) t.Run("locator identity matrix", func(t *testing.T) { tests := []struct { name string mutate func(*agenttask.LocatorRecord) }{ {"unknown kind", func(locator *agenttask.LocatorRecord) { locator.Kind = "future_locator" }}, {"empty opaque", func(locator *agenttask.LocatorRecord) { locator.Opaque = "" }}, {"missing revision", func(locator *agenttask.LocatorRecord) { locator.Revision = "" }}, {"project drift", func(locator *agenttask.LocatorRecord) { locator.ProjectID = "other-project" }}, {"workspace drift", func(locator *agenttask.LocatorRecord) { locator.WorkspaceID = "other-workspace" }}, {"work drift", func(locator *agenttask.LocatorRecord) { locator.WorkUnitID = "other-work" }}, {"attempt drift", func(locator *agenttask.LocatorRecord) { locator.AttemptID = "other-attempt" }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { rec := validRecord() test.mutate(&rec.Locators[0]) if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected ErrInvalidIdentity, got: %v", err) } }) } }) t.Run("empty or whitespace project identity", func(t *testing.T) { rec := validRecord() rec.ProjectID = "" if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected ErrInvalidIdentity for empty ProjectID, got: %v", err) } rec = validRecord() rec.WorkspaceID = " ws-001 " if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected ErrInvalidIdentity for padded WorkspaceID, got: %v", err) } }) t.Run("zero timestamp", func(t *testing.T) { rec := validRecord() rec.Timestamp = time.Time{} if err := rec.Validate(); !errors.Is(err, ErrInvalidTimestamp) { t.Fatalf("expected ErrInvalidTimestamp, got: %v", err) } }) t.Run("archive manifest validation and creation", func(t *testing.T) { now := recordTestNow rec1 := validRecord() rec1.Sequence = 10 rec2 := validRecord() rec2.RecordID = "rec-002" rec2.Sequence = 11 rec2.EventType = agenttask.EventCompleted rec2.State = agenttask.WorkStateCompleted rec2.StateRevision = "rev-2" rec2.Terminal = true manifest, err := NewArchiveManifest("proj-001", "ws-001", 1, []ProjectLogRecord{rec1, rec2}, now, true) if err != nil { t.Fatalf("NewArchiveManifest failed: %v", err) } if manifest.RecordCount != 2 { t.Errorf("expected RecordCount 2, got %d", manifest.RecordCount) } if manifest.FirstSequence != 10 || manifest.LastSequence != 11 { t.Errorf("expected sequence 10-11, got %d-%d", manifest.FirstSequence, manifest.LastSequence) } if !strings.HasPrefix(manifest.Checksum, "sha256:") { t.Errorf("expected sha256 checksum prefix, got %q", manifest.Checksum) } if !manifest.Terminal { t.Error("manifest terminal = false, want derived true") } manifest.SchemaVersion = 999 if err := manifest.Validate(); !errors.Is(err, ErrInvalidSchemaVersion) { t.Fatalf("expected ErrInvalidSchemaVersion for manifest, got: %v", err) } }) } func TestRecordRejectsSensitiveOrUnboundedFields(t *testing.T) { t.Run("oversized message rejected", func(t *testing.T) { rec := validRecord() rec.Message = strings.Repeat("a", MaxMessageLength+1) if err := rec.Validate(); !errors.Is(err, ErrUnboundedField) { t.Fatalf("expected ErrUnboundedField for oversized message, got: %v", err) } }) t.Run("oversized metadata keys count rejected", func(t *testing.T) { rec := validRecord() rec.Metadata = make(map[string]string) for i := 0; i < MaxMetadataKeys+1; i++ { rec.Metadata[string(rune('a'+i))] = "val" } if err := rec.Validate(); !errors.Is(err, ErrUnboundedField) { t.Fatalf("expected ErrUnboundedField for oversized metadata keys, got: %v", err) } }) t.Run("oversized locators count rejected", func(t *testing.T) { rec := validRecord() rec.Locators = make([]agenttask.LocatorRecord, MaxLocatorsCount+1) for i := range rec.Locators { rec.Locators[i] = agenttask.LocatorRecord{ Kind: agenttask.LocatorProcess, Opaque: "proc", Revision: "locator-rev", ProjectID: rec.ProjectID, WorkspaceID: rec.WorkspaceID, WorkUnitID: rec.WorkUnitID, AttemptID: rec.AttemptID, } } if err := rec.Validate(); !errors.Is(err, ErrUnboundedField) { t.Fatalf("expected ErrUnboundedField for oversized locators count, got: %v", err) } }) t.Run("secret tokens in message rejected", func(t *testing.T) { secrets := []string{ "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "API token is sk-proj-1234567890abcdef", "GitHub token ghp_123456789012345678901234567890123456", "AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3...", } for _, sec := range secrets { rec := validRecord() rec.Message = sec if err := rec.Validate(); !errors.Is(err, ErrSensitiveContent) { t.Errorf("expected ErrSensitiveContent for message %q, got: %v", sec, err) } } rec := validRecord() rec.RouteStatus.SelectionRevision = "" rec.RouteStatus.ReasonCode = "" if err := rec.Validate(); err != nil { t.Fatalf("optional selection evidence rejected: %v", err) } }) t.Run("change-set and integration evidence are exact when present", func(t *testing.T) { rec := validRecord() rec.ChangeSetID = "change-1" rec.ChangeSetRevision = "change-rev-1" rec.IntegrationAttempt = 2 rec.IntegrationOutcome = agenttask.IntegrationOutcomeIntegrated if err := rec.Validate(); err != nil { t.Fatalf("exact integration evidence rejected: %v", err) } tests := []struct { name string mutate func(*ProjectLogRecord) }{ {"revision without change set", func(rec *ProjectLogRecord) { rec.ChangeSetRevision = "change-rev-1" }}, {"change set without revision", func(rec *ProjectLogRecord) { rec.ChangeSetID = "change-1" }}, {"outcome without attempt", func(rec *ProjectLogRecord) { rec.ChangeSetID = "change-1" rec.ChangeSetRevision = "change-rev-1" rec.IntegrationOutcome = agenttask.IntegrationOutcomeIntegrated }}, {"unknown outcome", func(rec *ProjectLogRecord) { rec.ChangeSetID = "change-1" rec.ChangeSetRevision = "change-rev-1" rec.IntegrationAttempt = 1 rec.IntegrationOutcome = "future" }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { rec := validRecord() rec.ChangeSetID = "" rec.ChangeSetRevision = "" rec.IntegrationAttempt = 0 rec.IntegrationOutcome = "" test.mutate(&rec) if err := rec.Validate(); !errors.Is(err, ErrInvalidIdentity) { t.Fatalf("expected ErrInvalidIdentity, got %v", err) } }) } }) t.Run("raw environment dump in message rejected", func(t *testing.T) { rec := validRecord() rec.Message = "Environment PATH=/usr/local/bin:/usr/bin:/bin" if err := rec.Validate(); !errors.Is(err, ErrSensitiveContent) { t.Fatalf("expected ErrSensitiveContent for raw env in message, got: %v", err) } }) t.Run("sensitive metadata keys rejected", func(t *testing.T) { sensitiveKeys := []string{"api_key", "my_password", "auth_token", "env", "stderr_output"} for _, k := range sensitiveKeys { rec := validRecord() rec.Metadata = map[string]string{k: "value"} if err := rec.Validate(); !errors.Is(err, ErrSensitiveContent) { t.Errorf("expected ErrSensitiveContent for metadata key %q, got: %v", k, err) } } }) t.Run("sensitive locator opaque rejected", func(t *testing.T) { rec := validRecord() rec.Locators = []agenttask.LocatorRecord{ { Kind: agenttask.LocatorProcess, Opaque: "proc-with-sk-secret-token", Revision: "locator-rev", ProjectID: rec.ProjectID, WorkspaceID: rec.WorkspaceID, WorkUnitID: rec.WorkUnitID, AttemptID: rec.AttemptID, }, } if err := rec.Validate(); !errors.Is(err, ErrSensitiveContent) { t.Fatalf("expected ErrSensitiveContent for sensitive locator opaque, got: %v", err) } }) } func terminalArchiveRecords() []ProjectLogRecord { first := validRecord() first.RecordID = "rec-010" first.Sequence = 10 last := validRecord() last.RecordID = "rec-011" last.Sequence = 11 last.EventType = agenttask.EventCompleted last.State = agenttask.WorkStateCompleted last.StateRevision = "rev-2" last.Terminal = true return []ProjectLogRecord{first, last} } func TestArchiveManifestValidationMatrix(t *testing.T) { t.Run("valid archive is deterministic", func(t *testing.T) { records := terminalArchiveRecords() first, err := NewArchiveManifest("proj-001", "ws-001", 1, records, recordTestNow, true) if err != nil { t.Fatalf("first NewArchiveManifest() error = %v", err) } second, err := NewArchiveManifest("proj-001", "ws-001", 1, records, recordTestNow, true) if err != nil { t.Fatalf("second NewArchiveManifest() error = %v", err) } firstJSON, _ := json.Marshal(first) secondJSON, _ := json.Marshal(second) if string(firstJSON) != string(secondJSON) { t.Fatalf("manifest is not deterministic:\n%s\n%s", firstJSON, secondJSON) } }) t.Run("constructor rejects membership order and terminal drift", func(t *testing.T) { tests := []struct { name string ordinal uint64 terminal bool mutate func([]ProjectLogRecord) []ProjectLogRecord }{ {"empty archive", 1, true, func([]ProjectLogRecord) []ProjectLogRecord { return nil }}, {"zero ordinal", 0, true, func(records []ProjectLogRecord) []ProjectLogRecord { return records }}, {"project drift", 1, true, func(records []ProjectLogRecord) []ProjectLogRecord { records[0].ProjectID = "other-project" records[0].Locators[0].ProjectID = "other-project" return records }}, {"workspace drift", 1, true, func(records []ProjectLogRecord) []ProjectLogRecord { records[0].WorkspaceID = "other-workspace" records[0].Locators[0].WorkspaceID = "other-workspace" return records }}, {"duplicate sequence", 1, true, func(records []ProjectLogRecord) []ProjectLogRecord { records[1].Sequence = records[0].Sequence return records }}, {"descending sequence", 1, true, func(records []ProjectLogRecord) []ProjectLogRecord { records[1].Sequence = records[0].Sequence - 1 return records }}, {"early terminal", 1, true, func(records []ProjectLogRecord) []ProjectLogRecord { records[0].EventType = agenttask.EventCompleted records[0].State = agenttask.WorkStateCompleted records[0].Terminal = true return records }}, {"missing final terminal", 1, false, func(records []ProjectLogRecord) []ProjectLogRecord { records[1].EventType = agenttask.EventDispatchStarted records[1].State = agenttask.WorkStateDispatching records[1].Terminal = false return records }}, {"caller terminal mismatch", 1, false, func(records []ProjectLogRecord) []ProjectLogRecord { return records }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { records := test.mutate(terminalArchiveRecords()) if _, err := NewArchiveManifest( "proj-001", "ws-001", test.ordinal, records, recordTestNow, test.terminal, ); err == nil { t.Fatal("expected archive construction error") } }) } }) t.Run("decoded manifest shape is strict", func(t *testing.T) { base, err := NewArchiveManifest( "proj-001", "ws-001", 1, terminalArchiveRecords(), recordTestNow, true, ) if err != nil { t.Fatalf("NewArchiveManifest() error = %v", err) } tests := []struct { name string mutate func(*ArchiveManifest) }{ {"zero ordinal", func(manifest *ArchiveManifest) { manifest.ArchiveOrdinal = 0 }}, {"empty record count", func(manifest *ArchiveManifest) { manifest.RecordCount = 0 }}, {"inverted endpoints", func(manifest *ArchiveManifest) { manifest.LastSequence = manifest.FirstSequence - 1 }}, {"uppercase checksum", func(manifest *ArchiveManifest) { manifest.Checksum = strings.ToUpper(manifest.Checksum) }}, {"short checksum", func(manifest *ArchiveManifest) { manifest.Checksum = "sha256:abcd" }}, {"non-terminal", func(manifest *ArchiveManifest) { manifest.Terminal = false }}, {"empty work unit", func(manifest *ArchiveManifest) { manifest.WorkUnitIDs[0] = "" }}, {"oversized work unit", func(manifest *ArchiveManifest) { manifest.WorkUnitIDs[0] = agenttask.WorkUnitID(strings.Repeat("w", MaxIDLength+1)) }}, {"duplicate work unit", func(manifest *ArchiveManifest) { manifest.WorkUnitIDs = append(manifest.WorkUnitIDs, manifest.WorkUnitIDs[0]) }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { manifest := base manifest.WorkUnitIDs = append([]agenttask.WorkUnitID(nil), base.WorkUnitIDs...) test.mutate(&manifest) if err := manifest.Validate(); err == nil { t.Fatal("expected manifest validation error") } }) } }) }