Skip to content

Conversation

@noah-tutt-praetorian
Copy link
Collaborator

@noah-tutt-praetorian noah-tutt-praetorian commented Sep 15, 2025

Summary

  • Add Conversation model with name-to-UUID mapping following tableModel conventions
  • Add Message model with KSUID-based ordering for chronological sequencing
  • Support running capabilities integration via actor differentiation

Key Features

  • DynamoDB Integration: Uses baseTableModel with proper dynamodb labels
  • Key Patterns: #conversation#name#uuid and #message#conversationId#ksuid
  • KSUID Ordering: Natural timestamp ordering for message sequences
  • Registry Integration: Proper model registration following Tabularium patterns

Test Coverage

  • Comprehensive unit tests for both models
  • KSUID ordering validation
  • Registry integration testing
  • Model validation and hook execution tests

🤖 Generated with Claude Code

- Add Conversation model with name-to-UUID mapping following tableModel conventions
- Add Message model with KSUID-based ordering for chronological sequencing
- Implement baseTableModel with DynamoDB labels for table storage
- Key patterns: #conversation#name#uuid and #message#conversationId#ksuid
- Actor field supports chariot/user differentiation for capability outputs
- Include comprehensive test coverage with MockAWS patterns
- Follow established Tabularium registry patterns for model registration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

Walkthrough

Adds Conversation and Message models with defaults, validation, and registry hooks (Conversation generates UUIDs; Message generates KSUIDs, timestamps, and TTL). Registers both models. Introduces PlannerEvent, PlannerJobCompletion, and PlannerUserMessage structs. Extends Job with Conversation and User fields. Updates client/api.yaml to add conversation and message schemas and job.conversation. Adds comprehensive tests for Conversation, Message, and Job serialization/behavior. Updates go.mod to include an indirect dependency on github.com/segmentio/ksuid v1.0.4.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add conversations/messages models for running capabilities" accurately and concisely captures the primary change: introducing Conversation and Message models to support running capabilities, matching the PR objectives and file changes. It is a single, clear sentence without extraneous noise and would be understandable to a reviewer scanning history. The phrasing aligns with the implemented features (models, KSUID keys, registry integration) and is on-topic.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 54e3da5 and 44d9db2.

📒 Files selected for processing (1)
  • pkg/model/model/planner_event.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/model/model/planner_event.go

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 15, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
pkg/model/model/conversation.go (1)

43-45: Consider sanitizing the Name field in the Key to prevent delimiter conflicts.

The Key construction directly concatenates the Name field without sanitization. If the Name contains # characters, it could interfere with the Key's delimiter-based structure and potentially cause parsing issues.

Consider sanitizing the Name field before including it in the Key:

 Call: func() error {
     if c.Key == "" {
         conversationID := uuid.New().String()
-        c.Key = "#conversation#" + c.Name + "#" + conversationID
+        // Replace # with a safe character to prevent delimiter conflicts
+        safeName := strings.ReplaceAll(c.Name, "#", "_")
+        c.Key = "#conversation#" + safeName + "#" + conversationID
     }
     return nil
 },

You'll need to add "strings" to the imports.

pkg/model/model/message_test.go (1)

206-216: Consider using a more reliable timing mechanism for KSUID ordering tests.

The test uses time.Sleep(1 * time.Millisecond) to ensure different timestamps. This approach might be flaky in CI environments under high load.

Consider using a mock time provider or a more deterministic approach:

-// Small delay to ensure different timestamps
-time.Sleep(1 * time.Millisecond)
+// Use a small counter-based approach for more deterministic testing
+// KSUIDs include both timestamp and randomness, so even same-millisecond
+// KSUIDs will be unique and sortable

Actually, upon further consideration, KSUIDs are designed to be unique even when generated in the same millisecond due to their random component. The sleep might not be necessary at all.

pkg/model/model/message.go (2)

47-52: Consider sanitizing ConversationID in the Key to prevent delimiter conflicts.

Similar to the Conversation model, the Key construction directly concatenates the ConversationID without sanitization. While ConversationIDs are typically UUIDs and safe, it's good practice to ensure delimiter integrity.

Consider validating or sanitizing the ConversationID:

 Call: func() error {
     if m.Key == "" {
         if m.MessageID == "" {
             m.MessageID = ksuid.New().String()
         }
+        // Ensure ConversationID doesn't contain delimiter characters
+        if strings.Contains(m.ConversationID, "#") {
+            return fmt.Errorf("ConversationID cannot contain '#' character")
+        }
         m.Key = "#message#" + m.ConversationID + "#" + m.MessageID
     }
     return nil
 },

You'll need to add "strings" and "fmt" to the imports.


39-41: Potential duplicate KSUID generation.

The Defaulted() method generates a KSUID if MessageID is empty, but the hook also does the same check and generation. This duplication could be simplified.

Consider removing the KSUID generation from Defaulted() since the hook will handle it:

 func (m *Message) Defaulted() {
     m.Status = "sent"
     m.Timestamp = Now()
     m.TTL = Future(24 * 30) // 30 days
-    if m.MessageID == "" {
-        m.MessageID = ksuid.New().String()
-    }
 }

The hook already handles this case, so having it in both places is redundant.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f69ae7 and 3de047c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • go.mod (1 hunks)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/conversation_test.go (1 hunks)
  • pkg/model/model/message.go (1 hunks)
  • pkg/model/model/message_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/message.go
  • pkg/model/model/conversation_test.go
  • pkg/model/model/message_test.go
  • pkg/model/model/conversation.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Use the standard Go testing framework (testing package); do not introduce additional test frameworks

Files:

  • pkg/model/model/conversation_test.go
  • pkg/model/model/message_test.go
🧬 Code graph analysis (4)
pkg/model/model/message.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
pkg/model/model/conversation_test.go (4)
pkg/model/model/conversation.go (2)
  • NewConversation (57-65)
  • Conversation (8-19)
pkg/registry/registry.go (1)
  • Name (30-36)
pkg/model/model/globals.go (1)
  • Future (246-248)
pkg/registry/hook.go (1)
  • CallHooks (10-17)
pkg/model/model/message_test.go (3)
pkg/model/model/message.go (2)
  • NewMessage (64-74)
  • Message (8-21)
pkg/model/model/globals.go (1)
  • Future (246-248)
pkg/registry/hook.go (1)
  • CallHooks (10-17)
pkg/model/model/conversation.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
🔇 Additional comments (12)
go.mod (1)

34-34: LGTM! Necessary dependency for KSUID-based message ordering.

The addition of the github.com/segmentio/ksuid dependency is appropriate for implementing chronologically-ordered message IDs as described in the PR objectives.

pkg/model/model/conversation.go (3)

8-19: Model structure follows established patterns correctly.

The Conversation struct properly embeds registry.BaseModel and baseTableModel as required by the coding guidelines. All fields include the necessary dynamodbav, json, and desc struct tags for proper schema generation.


21-23: Model registration implemented correctly.

The model is properly registered in init() using registry.Registry.MustRegisterModel(&Conversation{}) as required by the coding guidelines.


57-65: Constructor implementation is clean and follows the pattern.

The NewConversation function properly initializes the struct, applies defaults, and runs hooks through the registry. The implementation is consistent with best practices.

pkg/model/model/conversation_test.go (3)

12-26: Comprehensive test coverage for constructor.

The test properly validates all aspects of the NewConversation function including field initialization, defaults, and Key generation.


168-219: Excellent security test coverage.

The security test scenarios comprehensively cover edge cases including special characters, SQL injection attempts, and XSS attempts. The tests correctly validate that these inputs are treated as regular strings without causing security issues.


155-166: Good test for Key uniqueness.

The test properly validates that multiple conversations with the same name generate unique Keys due to the UUID component.

pkg/model/model/message_test.go (2)

197-252: Excellent KSUID ordering validation.

The test thoroughly validates that KSUIDs maintain chronological ordering, which is crucial for the message sequencing feature. The test includes proper verification of both natural ordering and sorting capability.


295-372: Comprehensive security testing for message content.

The security tests properly validate handling of potentially malicious content including SQL injection and XSS attempts. All edge cases are appropriately covered.

pkg/model/model/message.go (3)

8-21: Model structure properly follows conventions.

The Message struct correctly embeds the required base types and includes all necessary struct tags (dynamodbav, json, desc, example) for proper schema generation.


23-25: Model registration implemented correctly.

The model is properly registered in init() following the required pattern.


64-74: Clean constructor implementation with proper initialization flow.

The NewMessage function follows the established pattern correctly, initializing fields, applying defaults, and running hooks.

Removes unnecessary complexity from conversation/message data models:

**Conversation Model Changes:**
- Removed Status field (no longer needed for simplified workflow)
- Removed Description field (not used in simplified implementation)
- Added UUID field for conversation reference and lookup
- Updated hooks to populate UUID when creating conversation key
- Simplified Defaulted() method to remove status initialization

**Message Model Changes:**
- Removed Status field (simplified message lifecycle)
- Added role constants: RoleUser, RoleChariot, RoleSystem
- Updated role field documentation to reference new constants
- Simplified Defaulted() method to remove status initialization

**Test Updates:**
- Updated all conversation tests to check UUID instead of Status
- Updated all message tests to remove Status field checks
- Added test for new role constants
- Maintained security scenario testing
- All tests passing with simplified model structure

These changes support the new simplified message-only API where conversations
are created automatically and messages always have user role assigned.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 15, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (8)
pkg/model/model/conversation.go (3)

32-35: Defaulted() overwrites pre-set values.

Only set Created/TTL when zero to avoid clobbering values provided by migrations or callers.

Apply:

 func (c *Conversation) Defaulted() {
-	c.Created = Now()
-	c.TTL = Future(24 * 30) // 30 days
+	if c.Created == "" {
+		c.Created = Now()
+	}
+	if c.TTL == 0 {
+		c.TTL = Future(24 * 30) // 30 days
+	}
 }

37-49: Key delimiter collision if Name contains '#'.

Name is concatenated into Key with '#'. Unescaped '#' in Name breaks parsing. Sanitize just the delimiter to preserve existing tests.

Apply:

 import (
 	"github.com/google/uuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
+	"strings"
 )
@@
 				if c.Key == "" {
 					conversationID := uuid.New().String()
 					c.UUID = conversationID
-					c.Key = "#conversation#" + c.Name + "#" + conversationID
+					safeName := strings.ReplaceAll(c.Name, "#", "%23")
+					c.Key = "#conversation#" + safeName + "#" + conversationID
 				}
 				return nil
 			},

Also applies to: 3-6


56-64: Hooks errors ignored; propagate from constructor.

Change NewConversation to return (Conversation, error) and bubble hook errors. This prevents silently returning invalid keys if hooks evolve to fail.

Apply:

-func NewConversation(name, username string) Conversation {
+func NewConversation(name, username string) (Conversation, error) {
 	conv := Conversation{
 		Name:     name,
 		Username: username,
 	}
 	conv.Defaulted()
-	registry.CallHooks(&conv)
-	return conv
+	if err := registry.CallHooks(&conv); err != nil {
+		return Conversation{}, err
+	}
+	return conv, nil
 }

Then update all call sites accordingly. Want me to open a follow-up PR to refactor usages?

pkg/model/model/message_test.go (3)

195-250: KSUID ordering test is tautological.

You sort both slices by MessageID and compare them; this can’t fail. Instead, verify that lexicographic KSUID order is non-decreasing by ksuid.Time().

Proposed approach:

sort.Slice(shuffled, func(i, j int) bool { return shuffled[i].MessageID < shuffled[j].MessageID })
prev := time.Unix(0,0)
for i := range shuffled {
    t1, _ := ksuid.Parse(shuffled[i].MessageID)
    if i > 0 && t1.Time().Before(prev) {
        t.Fatalf("KSUID order not chronological at index %d", i)
    }
    prev = t1.Time()
}

Also, the 1ms sleep doesn’t change the KSUID second component; you can drop it or increase to ≥1s if you need distinct seconds (not necessary with the check above).


15-33: Use example.com emails in tests.

Replace org emails with example.com to avoid accidental PII and external domain coupling.

Apply:

-username := "[email protected]"
+username := "[email protected]"
-username := "[email protected]"
+username := "[email protected]"

Also applies to: 293-301


303-351: Role “assistant” vs defined constants.

The code defines RoleChariot and RoleSystem, not “assistant.” Tests assuming “assistant” are fine today (Valid only checks non-empty) but will break if role validation tightens. Consider aligning on constants or explicitly documenting that any string role is allowed.

pkg/model/model/message.go (2)

69-79: Hooks errors ignored; propagate from constructor.

Return (Message, error) so callers can handle hook failures gracefully.

Apply:

-func NewMessage(conversationID, role, content, username string) Message {
+func NewMessage(conversationID, role, content, username string) (Message, error) {
 	msg := Message{
 		ConversationID: conversationID,
 		Role:           role,
 		Content:        content,
 		Username:       username,
 	}
 	msg.Defaulted()
-	registry.CallHooks(&msg)
-	return msg
+	if err := registry.CallHooks(&msg); err != nil {
+		return Message{}, err
+	}
+	return msg, nil
 }

Update call sites/tests accordingly.


65-67: Optional: constrain roles.

If roles should be limited to RoleUser/RoleChariot/RoleSystem, add a whitelist check in Valid() or a separate ValidateRole() helper.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3de047c and fd154fd.

📒 Files selected for processing (4)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/conversation_test.go (1 hunks)
  • pkg/model/model/message.go (1 hunks)
  • pkg/model/model/message_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/model/model/conversation_test.go
🧰 Additional context used
📓 Path-based instructions (2)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/conversation.go
  • pkg/model/model/message_test.go
  • pkg/model/model/message.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Use the standard Go testing framework (testing package); do not introduce additional test frameworks

Files:

  • pkg/model/model/message_test.go
🧬 Code graph analysis (3)
pkg/model/model/conversation.go (3)
pkg/registry/registry.go (2)
  • Name (30-36)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
pkg/model/model/message_test.go (3)
pkg/model/model/message.go (5)
  • NewMessage (69-79)
  • Message (15-27)
  • RoleUser (10-10)
  • RoleChariot (11-11)
  • RoleSystem (12-12)
pkg/model/model/globals.go (1)
  • Future (246-248)
pkg/registry/hook.go (1)
  • CallHooks (10-17)
pkg/model/model/message.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
🔇 Additional comments (3)
pkg/model/model/conversation.go (1)

11-17: Neo4j tags applicability.

Guidelines: include neo4j tags “where applicable.” If conversations participate in graph export, add neo4j tags; otherwise, confirm they are intentionally omitted.

pkg/model/model/message.go (2)

29-31: Model registration LGTM.


18-26: Neo4j tags applicability.

If messages are part of graph export, add neo4j tags; otherwise, explicitly confirm exclusion.

Comment on lines +9 to +12
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/segmentio/ksuid"
"github.com/praetorian-inc/tabularium/pkg/registry"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove testify; use standard testing.

Guidelines forbid additional test frameworks. Replace assert/require with plain testing helpers.

Example pattern:

t.Helper()
if got != want {
    t.Fatalf("GetKey() = %q, want %q", got, want)
}

Do you want me to push a patch converting this file to stdlib testing?

🤖 Prompt for AI Agents
In pkg/model/model/message_test.go around lines 9 to 12, the test file imports
testify's assert/require which violates guidelines; remove those imports and
replace all assert/require calls with standard testing helpers: add t.Helper()
where appropriate, use if checks and t.Fatalf/t.Errorf/t.FailNow for failures
(e.g. replace require.NoError(t, err) with if err != nil { t.Fatalf("...: %v",
err) } and replace assert.Equal(t, want, got) with if got != want {
t.Fatalf("... got %v want %v", got, want) }). Also update the import block to
remove testify packages (keep ksuid and registry), and adjust any helper
functions to accept *testing.T and call t.Helper().

Comment on lines 15 to 27
type Message struct {
registry.BaseModel
baseTableModel
Username string `dynamodbav:"username" json:"username" desc:"Username who sent the message." example:"[email protected]"`
Key string `dynamodbav:"key" json:"key" desc:"Unique key for the message." example:"#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK"`
// Attributes
ConversationID string `dynamodbav:"conversationId" json:"conversationId" desc:"ID of the conversation this message belongs to." example:"550e8400-e29b-41d4-a716-446655440000"`
Role string `dynamodbav:"role" json:"role" desc:"Role of the message sender (user, chariot, system)." example:"user"`
Content string `dynamodbav:"content" json:"content" desc:"Content of the message." example:"Hello, how can I help you today?"`
Timestamp string `dynamodbav:"timestamp" json:"timestamp" desc:"Timestamp when the message was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
MessageID string `dynamodbav:"messageId" json:"messageId" desc:"KSUID for message ordering." example:"1sB5tZfLipTVWQWHVKnDFS6kFRK"`
TTL int64 `dynamodbav:"ttl" json:"ttl" desc:"Time-to-live for the message record (Unix timestamp)." example:"1706353200"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm embedding requirement: BaseAsset vs BaseModel.

Same as Conversation: verify whether BaseAsset embedding is required per repo convention.

Run:


🏁 Script executed:

#!/bin/bash
rg -nPU --type=go '(?s)type\s+\w+\s+struct\s*\{[^}]*BaseAsset' pkg/model

Length of output: 550


Embed BaseAsset in Message (follow repo convention)

pkg/model/model/message.go (lines 15–27) currently embeds registry.BaseModel + baseTableModel; change it to embed BaseAsset to match other model types (see pkg/model/model/asset.go, ad_object.go, web_application.go, integration.go, repository.go).

🤖 Prompt for AI Agents
In pkg/model/model/message.go around lines 15 to 27, the Message struct
currently embeds registry.BaseModel and baseTableModel; update it to embed
BaseAsset instead to follow the repo convention used by other models (asset.go,
ad_object.go, web_application.go, integration.go, repository.go). Replace the
two embedded types with BaseAsset (ensure the type is imported or accessible in
this package), keep all existing fields unchanged, and run a quick build to fix
any references — adjust any field access or tests that relied on the old
embedded types if compilation reveals mismatches.

Comment on lines 49 to 63
func (m *Message) GetHooks() []registry.Hook {
return []registry.Hook{
{
Call: func() error {
if m.Key == "" {
if m.MessageID == "" {
m.MessageID = ksuid.New().String()
}
m.Key = "#message#" + m.ConversationID + "#" + m.MessageID
}
return nil
},
},
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Build Key only when ConversationID is present; return error otherwise.

Currently, hooks can generate a malformed key when ConversationID is empty. Make the hook fail fast and propagate the error.

Apply:

 import (
+	"fmt"
 	"github.com/segmentio/ksuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
 )
@@
 		{
 			Call: func() error {
 				if m.Key == "" {
+					if m.ConversationID == "" {
+						return fmt.Errorf("ConversationID is required to build message key")
+					}
 					if m.MessageID == "" {
 						m.MessageID = ksuid.New().String()
 					}
 					m.Key = "#message#" + m.ConversationID + "#" + m.MessageID
 				}
 				return nil
 			},
 		},
 	}
 }

Also applies to: 3-6

🤖 Prompt for AI Agents
In pkg/model/model/message.go around lines 49 to 63, the hook currently builds
m.Key even when ConversationID is empty which can produce malformed keys; change
the hook to validate that ConversationID is non-empty and return a descriptive
error if it is missing instead of silently constructing the key, and propagate
that error from the Call function; apply the same validation/error-return change
to the other affected locations noted (lines 3-6).

- Remove all code comments for cleaner implementation
- Simplify conversation key to UUID-only: #conversation#uuid
- Remove status fields from both models
- Update conversation model to use UUID as primary key
- Maintain role constants for message actors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 15, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
pkg/model/model/conversation.go (1)

8-17: Embed BaseAsset per repo convention.

Models under pkg/model/** must embed BaseAsset. Replace registry.BaseModel with BaseAsset; keep baseTableModel if needed.

 type Conversation struct {
-	registry.BaseModel
-	baseTableModel
+	BaseAsset
+	baseTableModel
 	Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]"`
 	Key      string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
 	Name        string `dynamodbav:"name" json:"name" desc:"Name of the conversation." example:"My AI Assistant Chat"`
 	UUID        string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
 	Created     string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
 	TTL         int64  `dynamodbav:"ttl" json:"ttl" desc:"Time-to-live for the conversation record (Unix timestamp)." example:"1706353200"`
 }
pkg/model/model/message.go (2)

14-25: Embed BaseAsset per repo convention.

Replace registry.BaseModel with BaseAsset; keep baseTableModel if needed.

 type Message struct {
-	registry.BaseModel
-	baseTableModel
+	BaseAsset
+	baseTableModel
 	Username       string `dynamodbav:"username" json:"username" desc:"Username who sent the message." example:"[email protected]"`
 	Key            string `dynamodbav:"key" json:"key" desc:"Unique key for the message." example:"#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK"`
 	ConversationID string `dynamodbav:"conversationId" json:"conversationId" desc:"ID of the conversation this message belongs to." example:"550e8400-e29b-41d4-a716-446655440000"`
 	Role           string `dynamodbav:"role" json:"role" desc:"Role of the message sender (user, chariot, system)." example:"user"`
 	Content        string `dynamodbav:"content" json:"content" desc:"Content of the message." example:"Hello, how can I help you today?"`
 	Timestamp      string `dynamodbav:"timestamp" json:"timestamp" desc:"Timestamp when the message was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
 	MessageID      string `dynamodbav:"messageId" json:"messageId" desc:"KSUID for message ordering." example:"1sB5tZfLipTVWQWHVKnDFS6kFRK"`
 	TTL            int64  `dynamodbav:"ttl" json:"ttl" desc:"Time-to-live for the message record (Unix timestamp)." example:"1706353200"`
 }

47-56: Guard against malformed message keys when ConversationID is empty.

Fail fast instead of producing “#message##ksuid”.

 import (
+	"fmt"
 	"github.com/segmentio/ksuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
 )
@@
 			Call: func() error {
 				if m.Key == "" {
+					if m.ConversationID == "" {
+						return fmt.Errorf("ConversationID is required to build message key")
+					}
 					if m.MessageID == "" {
 						m.MessageID = ksuid.New().String()
 					}
 					m.Key = "#message#" + m.ConversationID + "#" + m.MessageID
 				}
 				return nil
 			},

Also applies to: 3-6

🧹 Nitpick comments (6)
pkg/model/model/conversation.go (3)

3-6: Backfill UUID when Key is pre-set.

If Key is provided externally, UUID stays empty. Extract UUID from Key when possible.

 import (
+	"strings"
 	"github.com/google/uuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
 )
@@
-				if c.Key == "" {
+				if c.Key == "" {
 					conversationID := uuid.New().String()
 					c.UUID = conversationID
 					c.Key = "#conversation#" + conversationID
-				}
+				} else if c.UUID == "" && strings.HasPrefix(c.Key, "#conversation#") {
+					parts := strings.Split(c.Key, "#")
+					if len(parts) >= 3 {
+						c.UUID = parts[len(parts)-1]
+					}
+				}
 				return nil
 			},

Also applies to: 39-47


51-53: Include Username in validation.

Username appears required by design; ensure it’s non-empty.

-func (c *Conversation) Valid() bool {
-	return c.Name != ""
-}
+func (c *Conversation) Valid() bool {
+	return c.Name != "" && c.Username != ""
+}

55-63: Propagate hook errors from constructor.

registry.CallHooks returns error; current code drops it. Prefer returning (Conversation, error).

-func NewConversation(name, username string) Conversation {
+func NewConversation(name, username string) (Conversation, error) {
 	conv := Conversation{
 		Name:     name,
 		Username: username,
 	}
 	conv.Defaulted()
-	registry.CallHooks(&conv)
-	return conv
+	if err := registry.CallHooks(&conv); err != nil {
+		return Conversation{}, err
+	}
+	return conv, nil
 }

Please confirm callers can accept the signature change; otherwise we can log/record the error instead.

pkg/model/model/message.go (3)

63-65: Tighten validation (require Username and restrict Role to known values).

-func (m *Message) Valid() bool {
-	return m.ConversationID != "" && m.Role != "" && m.Content != ""
-}
+func (m *Message) Valid() bool {
+	if m.ConversationID == "" || m.Username == "" || m.Content == "" {
+		return false
+	}
+	switch m.Role {
+	case RoleUser, RoleChariot, RoleSystem:
+		return true
+	default:
+		return false
+	}
+}

67-76: Propagate hook errors from constructor.

Return (Message, error) to surface hook failures.

-func NewMessage(conversationID, role, content, username string) Message {
+func NewMessage(conversationID, role, content, username string) (Message, error) {
 	msg := Message{
 		ConversationID: conversationID,
 		Role:           role,
 		Content:        content,
 		Username:       username,
 	}
 	msg.Defaulted()
-	registry.CallHooks(&msg)
-	return msg
+	if err := registry.CallHooks(&msg); err != nil {
+		return Message{}, err
+	}
+	return msg, nil
 }

8-12: Optional: define a typed Role.

Improves type-safety and discoverability.

-const (
-	RoleUser    = "user"
-	RoleChariot = "chariot"
-	RoleSystem  = "system"
-)
+type Role string
+
+const (
+	RoleUser    Role = "user"
+	RoleChariot Role = "chariot"
+	RoleSystem  Role = "system"
+)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fd154fd and 2df2715.

📒 Files selected for processing (2)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/message.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/message.go
  • pkg/model/model/conversation.go
🧠 Learnings (1)
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Model structs must embed BaseAsset

Applied to files:

  • pkg/model/model/message.go
  • pkg/model/model/conversation.go
🧬 Code graph analysis (2)
pkg/model/model/message.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
pkg/model/model/conversation.go (2)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-code-action / claude-code-action
🔇 Additional comments (4)
pkg/model/model/conversation.go (1)

19-21: Model registration looks correct.

pkg/model/model/message.go (3)

39-45: Defaults look good (timestamp, TTL, KSUID).


27-29: Model registration looks correct.


17-25: Confirm whether Message struct should include neo4j tags

pkg/model/model/message.go (lines 17–25) currently has no neo4j tags; several other models (person.go, technology.go, relationships.go, web_application.go, cpe.go) do. If messages are represented in graph views, add neo4j tags to key fields (Username, Key, ConversationID, MessageID, Timestamp); otherwise omission is acceptable.

Comment on lines 11 to 16
Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]"`
Key string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
Name string `dynamodbav:"name" json:"name" desc:"Name of the conversation." example:"My AI Assistant Chat"`
UUID string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
Created string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
TTL int64 `dynamodbav:"ttl" json:"ttl" desc:"Time-to-live for the conversation record (Unix timestamp)." example:"1706353200"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

neo4j tags applicability.

If these models participate in graph export, add neo4j tags; otherwise confirm they’re intentionally omitted.


🏁 Script executed:

#!/bin/bash
# Scan for neo4j tag usage patterns in existing models to align conventions.
rg -n --type=go 'neo4j:"' pkg/model | head -n 50

Length of output: 8385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== conversation.go (with line numbers) ==="
if [ -f pkg/model/model/conversation.go ]; then
  nl -ba pkg/model/model/conversation.go | sed -n '1,240p'
else
  echo "pkg/model/model/conversation.go not found"
fi

echo
echo "=== struct definition match: 'type Conversation' ==="
rg -n --type=go '^\s*type\s+Conversation\b' || true

echo
echo "=== all occurrences of 'Conversation' in repo ==="
rg -n --hidden --no-ignore-vcs '\bConversation\b' || true

echo
echo "=== files under pkg/model/model that contain neo4j struct tags ==="
rg -n --type=go 'neo4j:"' pkg/model/model || true

echo
echo "=== occurrences of 'neo4j' across repo (imports, tags, code) ==="
rg -n --hidden --no-ignore-vcs 'neo4j' || true

echo
echo "=== look for code that exports/models -> neo4j or mentions graph export helpers ==="
rg -n --hidden --no-ignore-vcs -S '\b(ToNeo4j|ToGraph|ExportToNeo|ExportToGraph|GraphExport|ExportGraph|neo4jExport)\b' || true

Length of output: 84


Add neo4j struct tags to Conversation model or confirm intentional omission.

Conversation at pkg/model/model/conversation.go currently lacks neo4j tags while peer models (e.g., person.go, organization.go) include them — either add neo4j:"<field_name>" tags (matching the json names) to the Conversation fields or state that this model is intentionally excluded from graph export.

🤖 Prompt for AI Agents
In pkg/model/model/conversation.go around lines 11 to 16, the Conversation
struct is missing neo4j struct tags while peer models include them; either add
neo4j:"<field_name>" tags matching the json names for each field (e.g.,
neo4j:"username", neo4j:"key", neo4j:"name", neo4j:"uuid", neo4j:"created",
neo4j:"ttl") to enable graph export consistency, or if this model is
intentionally excluded from graph export, add a clear comment above the struct
explaining the intentional omission so reviewers and tooling know it was
deliberate.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 18, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
client/api.yaml (3)

1099-1138: Add UUID format to conversation.uuid.

Improve schema validation by marking uuid fields as UUIDs.

         uuid:
           description: UUID of the conversation for reference.
           example: 550e8400-e29b-41d4-a716-446655440000
-          type: string
+          type: string
+          format: uuid

1767-1771: Mark job.conversation as a UUID.

Align with description and downstream usage.

         conversation:
           description: UUID of the conversation that initiated this job.
           example: 550e8400-e29b-41d4-a716-446655440000
-          type: string
+          type: string
+          format: uuid

1903-1952: Tighten message schema (UUIDs, timestamp, role enum).

  • conversationId: uuid
  • timestamp: date-time
  • role: restrict to known values
       properties:
         BaseModel:
           type: object
         content:
           description: Content of the message.
           example: Hello, how can I help you today?
           type: string
         conversationId:
           description: ID of the conversation this message belongs to.
           example: 550e8400-e29b-41d4-a716-446655440000
-          type: string
+          type: string
+          format: uuid
         key:
           description: Unique key for the message.
           example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK'
           type: string
         messageId:
           description: KSUID for message ordering.
           example: 1sB5tZfLipTVWQWHVKnDFS6kFRK
           type: string
         role:
           description: Role of the message sender (user, chariot, system).
           example: user
-          type: string
+          type: string
+          enum: [user, chariot, system]
         timestamp:
           description: Timestamp when the message was created (RFC3339).
           example: "2023-10-27T10:00:00Z"
-          type: string
+          type: string
+          format: date-time
modules/chariot/backend/cmd/listeners/planner-events/main.go (1)

36-42: Guard against empty error text on failures.

Avoid rendering a blank “Error:” line.

-		builder.WriteString(fmt.Sprintf("**Error**: %s\n", event.Comment))
+		errText := event.Comment
+		if strings.TrimSpace(errText) == "" {
+			errText = "No error provided."
+		}
+		builder.WriteString(fmt.Sprintf("**Error**: %s\n", errText))
pkg/model/model/planner_event.go (2)

41-52: Cap ResultKeys and bound key length in hook.

  • Enforce the documented “first 20 result keys”.
  • Protect against oversized partition/sort keys.
 func (p *PlannerEvent) GetHooks() []registry.Hook {
 	return []registry.Hook{
 		{
 			Call: func() error {
 				if p.Key == "" {
 					p.Key = "#plannerevent#" + p.ConversationID + "#" + p.JobKey
 				}
+				if len(p.ResultKeys) > 20 {
+					p.ResultKeys = p.ResultKeys[:20]
+				}
+				if len(p.Key) > 1024 {
+					p.Key = p.Key[:1024]
+				}
 				return nil
 			},
 		},
 	}
 }

54-56: Tighten Valid() with status whitelist.

Only accept known terminal job statuses.

-func (p *PlannerEvent) Valid() bool {
-	return p.ConversationID != "" && p.JobKey != "" && p.Source != ""
-}
+func (p *PlannerEvent) Valid() bool {
+	if p.ConversationID == "" || p.JobKey == "" || p.Source == "" {
+		return false
+	}
+	switch p.Status {
+	case "JP", "JF":
+		return true
+	default:
+		return false
+	}
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2df2715 and 0f9969b.

📒 Files selected for processing (6)
  • client/api.yaml (3 hunks)
  • modules/chariot/backend/cmd/listeners/planner-events/main.go (1 hunks)
  • pkg/model/model/job.go (1 hunks)
  • pkg/model/model/job_test.go (1 hunks)
  • pkg/model/model/planner_event.go (1 hunks)
  • pkg/model/model/planner_event_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pkg/model/model/planner_event_test.go
🧰 Additional context used
📓 Path-based instructions (2)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/job.go
  • pkg/model/model/job_test.go
  • pkg/model/model/planner_event.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Use the standard Go testing framework (testing package); do not introduce additional test frameworks

Files:

  • pkg/model/model/job_test.go
🧬 Code graph analysis (4)
pkg/model/model/job.go (1)
pkg/model/model/conversation.go (1)
  • Conversation (8-17)
pkg/model/model/job_test.go (3)
pkg/model/model/asset.go (1)
  • NewAsset (213-223)
pkg/model/model/job.go (1)
  • NewJob (178-187)
pkg/model/model/conversation.go (1)
  • Conversation (8-17)
pkg/model/model/planner_event.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
modules/chariot/backend/cmd/listeners/planner-events/main.go (2)
pkg/model/model/planner_event.go (1)
  • PlannerEvent (7-22)
pkg/model/model/message.go (2)
  • NewMessage (67-77)
  • RoleChariot (10-10)
🔇 Additional comments (3)
pkg/model/model/job.go (1)

36-36: LGTM: job.conversation field matches API and omitempty semantics.

If you want conversation info available in execution context, consider adding it to ResultContext and plumbing it through callers.

pkg/model/model/job_test.go (1)

258-295: Solid coverage for JSON omit/include behavior.

Tests clearly assert presence/absence and value retention. No issues.

pkg/model/model/planner_event.go (1)

36-39: Defaults look good.

TTL/default timestamps align with model patterns.

Comment on lines 15 to 22
func handler(ctx context.Context, sqsEvent events.SQSEvent) error {
for _, record := range sqsEvent.Records {
if err := processMessage(ctx, record.Body); err != nil {
slog.Error("failed to process message", "error", err, "messageId", record.MessageId)
}
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t swallow SQS failures — use partial batch response.

Logging and returning nil drops failed messages. Switch to SQS partial batch so only failed records are retried.

-func handler(ctx context.Context, sqsEvent events.SQSEvent) error {
-	for _, record := range sqsEvent.Records {
-		if err := processMessage(ctx, record.Body); err != nil {
-			slog.Error("failed to process message", "error", err, "messageId", record.MessageId)
-		}
-	}
-	return nil
-}
+func handler(ctx context.Context, sqsEvent events.SQSEvent) (events.SQSEventResponse, error) {
+	var resp events.SQSEventResponse
+	for _, record := range sqsEvent.Records {
+		if err := processMessage(ctx, record.Body); err != nil {
+			slog.Error("failed to process message", "error", err, "messageId", record.MessageId)
+			resp.BatchItemFailures = append(resp.BatchItemFailures, events.SQSBatchItemFailure{
+				ItemIdentifier: record.MessageId,
+			})
+		}
+	}
+	return resp, nil
+}
@@
-func main() {
-	lambda.Start(handler)
-}
+func main() {
+	lambda.Start(handler)
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func handler(ctx context.Context, sqsEvent events.SQSEvent) error {
for _, record := range sqsEvent.Records {
if err := processMessage(ctx, record.Body); err != nil {
slog.Error("failed to process message", "error", err, "messageId", record.MessageId)
}
}
return nil
}
func handler(ctx context.Context, sqsEvent events.SQSEvent) (events.SQSEventResponse, error) {
var resp events.SQSEventResponse
for _, record := range sqsEvent.Records {
if err := processMessage(ctx, record.Body); err != nil {
slog.Error("failed to process message", "error", err, "messageId", record.MessageId)
resp.BatchItemFailures = append(resp.BatchItemFailures, events.SQSBatchItemFailure{
ItemIdentifier: record.MessageId,
})
}
}
return resp, nil
}
🤖 Prompt for AI Agents
In modules/chariot/backend/cmd/listeners/planner-events/main.go around lines
15–22, the handler currently logs failures and returns nil which drops failed
SQS messages; change the handler to use SQS partial-batch response by collecting
failed record IDs as you process the loop and returning an SQS partial-batch
response containing those failed message IDs so AWS will retry only the failures
(instead of returning nil). Ensure the function returns the appropriate SQS
batch response type (with FailedEntries/IDs) and a nil error on normal
completion, and populate the failed IDs when processMessage returns an error.

Comment on lines 33 to 63
func processJobCompletion(ctx context.Context, event model.PlannerEvent) error {
var builder strings.Builder

if event.Status == "JF" {
builder.WriteString("🔴 **Security Scan Failed**\n\n")
builder.WriteString(fmt.Sprintf("**Capability**: %s\n", event.Source))
builder.WriteString(fmt.Sprintf("**Target**: %s\n", event.Target))
builder.WriteString(fmt.Sprintf("**Error**: %s\n", event.Comment))
builder.WriteString("**Recommendation**: Check target accessibility and try again.")
} else {
builder.WriteString("🎯 **Security Scan Complete**\n\n")
builder.WriteString(fmt.Sprintf("**Capability**: %s\n", event.Source))
builder.WriteString(fmt.Sprintf("**Target**: %s\n", event.Target))
builder.WriteString(fmt.Sprintf("**Total Findings**: %d\n", event.TotalCount))

if len(event.ResultKeys) > 0 {
builder.WriteString("\n**Key Findings**:\n")
for i, key := range event.ResultKeys {
if i >= 5 {
builder.WriteString(fmt.Sprintf("... and %d more findings\n", len(event.ResultKeys)-5))
break
}
builder.WriteString(fmt.Sprintf("- %s\n", key))
}
}

if event.TotalCount > len(event.ResultKeys) {
builder.WriteString(fmt.Sprintf("\n*Total: %d findings (%d additional not shown)*", event.TotalCount, event.TotalCount-len(event.ResultKeys)))
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate incoming events before processing.

Reject invalid payloads early using model validation.

 func processJobCompletion(ctx context.Context, event model.PlannerEvent) error {
+	if !event.Valid() {
+		return fmt.Errorf("invalid planner event: missing required fields")
+	}
 	var builder strings.Builder

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In modules/chariot/backend/cmd/listeners/planner-events/main.go around lines 33
to 63, add early validation of the incoming event at the top of
processJobCompletion and return an error for invalid payloads: verify required
fields (Status present and one of expected codes like "JF" or success, Source
and Target non-empty, TotalCount non-negative, ResultKeys non-nil) or call an
existing model validation helper (e.g., model.Validate(event)) if available; if
validation fails, log the reason and return a descriptive error without
continuing to build the message.

Comment on lines 66 to 71
aws := cloud.NewAWS(event.Username)
aiMsg := model.NewMessage(event.ConversationID, model.RoleChariot, summary, "system")

if err := aws.Table.Insert(&aiMsg); err != nil {
return fmt.Errorf("failed to store job completion summary: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the user’s username for the message item, not "system".

The message sender is role=chariot; username should still be the tenant/user to keep partitioning and ACLs sane.

-	aws := cloud.NewAWS(event.Username)
-	aiMsg := model.NewMessage(event.ConversationID, model.RoleChariot, summary, "system")
+	aws := cloud.NewAWS(event.Username)
+	aiMsg := model.NewMessage(event.ConversationID, model.RoleChariot, summary, event.Username)
🤖 Prompt for AI Agents
In modules/chariot/backend/cmd/listeners/planner-events/main.go around lines 66
to 71, the created aiMsg uses "system" as the username which breaks tenant
partitioning/ACLs; change the username argument to the event.Username when
constructing the message (keep RoleChariot as the role), so the stored message
is associated with the correct user before inserting into AWS.Table; verify any
downstream ACL/partitioning assumptions still hold.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
pkg/model/model/planner_event.go (4)

10-12: Align Key example with actual key composition.

Hook builds Key as "#plannerevent##", but the example shows only a conversation UUID. Update the example to avoid confusion.

Apply this diff:

-	Key            string   `dynamodbav:"key" json:"key" desc:"Unique key for the planner event." example:"#plannerevent#550e8400-e29b-41d4-a716-446655440000"`
+	Key            string   `dynamodbav:"key" json:"key" desc:"Unique key for the planner event." example:"#plannerevent#550e8400-e29b-41d4-a716-446655440000#job#example.com#10.0.1.5#nuclei#1698422400"`

17-18: Omit empty result keys to reduce noise.

Mark resultKeys as omitempty so empty slices don’t serialize.

-	ResultKeys     []string `dynamodbav:"resultKeys" json:"resultKeys" desc:"First 20 result keys from job execution." example:"[\"#asset#example.com#web01\"]"`
+	ResultKeys     []string `dynamodbav:"resultKeys" json:"resultKeys,omitempty" desc:"First 20 result keys from job execution." example:"[\"#asset#example.com#web01\"]"`

36-39: Don’t overwrite provided values in Defaulted().

Guard defaults to avoid clobbering prefilled values.

 func (p *PlannerEvent) Defaulted() {
-	p.CompletedAt = Now()
-	p.TTL = Future(24 * 7)
+	if p.CompletedAt == "" {
+		p.CompletedAt = Now()
+	}
+	if p.TTL == 0 {
+		p.TTL = Future(24 * 7)
+	}
 }

54-56: Validation: consider requiring Username (and optionally CompletedAt).

If events must be attributable, include Username in Valid(); CompletedAt is defaulted but can be validated too.

-func (p *PlannerEvent) Valid() bool {
-	return p.ConversationID != "" && p.JobKey != "" && p.Source != ""
-}
+func (p *PlannerEvent) Valid() bool {
+	return p.ConversationID != "" && p.JobKey != "" && p.Source != "" && p.Username != ""
+}
client/api.yaml (3)

1099-1138: Add formats for stronger schema: uuid/date-time.

Mark uuid and created with OpenAPI formats.

       created:
         description: Timestamp when the conversation was created (RFC3339).
         example: "2023-10-27T10:00:00Z"
-        type: string
+        type: string
+        format: date-time
@@
       uuid:
         description: UUID of the conversation for reference.
         example: 550e8400-e29b-41d4-a716-446655440000
-        type: string
+        type: string
+        format: uuid

1767-1770: Name consistency: prefer conversationId (matches message).

Job uses conversation while Message uses conversationId. Aligning avoids client confusion.

-        conversation:
-          description: UUID of the conversation that initiated this job.
-          example: 550e8400-e29b-41d4-a716-446655440000
-          type: string
+        conversationId:
+          description: UUID of the conversation that initiated this job.
+          example: 550e8400-e29b-41d4-a716-446655440000
+          type: string
+          format: uuid

Note: If you keep conversation for backward-compat, alias it or document both until clients migrate.


1907-1956: Tighten Message schema: uuid/date-time formats and role enum.

Add formats and constrain role values.

       conversationId:
         description: ID of the conversation this message belongs to.
         example: 550e8400-e29b-41d4-a716-446655440000
-        type: string
+        type: string
+        format: uuid
@@
       role:
         description: Role of the message sender (user, chariot, system).
         example: user
-        type: string
+        type: string
+        enum: [user, chariot, system]
@@
       timestamp:
         description: Timestamp when the message was created (RFC3339).
         example: "2023-10-27T10:00:00Z"
-        type: string
+        type: string
+        format: date-time
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0f9969b and f4ad8dd.

📒 Files selected for processing (4)
  • client/api.yaml (3 hunks)
  • pkg/model/model/job.go (1 hunks)
  • pkg/model/model/planner_event.go (1 hunks)
  • pkg/model/model/planner_event_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/model/model/planner_event_test.go
  • pkg/model/model/job.go
🧰 Additional context used
📓 Path-based instructions (1)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/planner_event.go
🧬 Code graph analysis (1)
pkg/model/model/planner_event.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (1)
  • Hook (4-7)
🔇 Additional comments (1)
pkg/model/model/planner_event.go (1)

24-26: Registration looks correct.

Model is registered in init() via MustRegisterModel; consistent with other models.

Comment on lines 41 to 52
func (p *PlannerEvent) GetHooks() []registry.Hook {
return []registry.Hook{
{
Call: func() error {
if p.Key == "" {
p.Key = "#plannerevent#" + p.ConversationID + "#" + p.JobKey
}
return nil
},
},
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Key embeds full JobKey (with ‘#’); verify downstream parsing.

Including JobKey inline yields “#plannerevent###job#…”. If any parsing assumes fixed segment counts, this may break. If needed, switch to a sanitized job identifier or delimiter-safe encoding.

Run to verify assumptions:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Look for code splitting keys on '#'
rg -nP -C2 'strings\.Split\([^,]+,\s*"#"\s*\)' --type=go

# Inspect any existing patterns for planner-event-like keys
rg -nP -C2 '#plannerevent#|plannerevent' || true

Length of output: 4790


Don't embed raw JobKey (which contains '#') into PlannerEvent.Key — sanitize or encode it.

JobKey examples include inner '#' segments (e.g. "#job#example.com#...") and the codebase splits keys on '#' (e.g. pkg/registry/wrapper.go and various tests). Current concat in pkg/model/model/planner_event.go (GetHooks) produces extra '#' segments and will break parsers that expect fixed segment counts.

  • Change: in pkg/model/model/planner_event.go (GetHooks) sanitize/escape/encode p.JobKey before concatenation (strip leading '#', replace/escape '#' or use base64/url-safe encoding).
  • Update: adjust pkg/model/model/planner_event_test.go expectations and audit any parsers that rely on fixed counts (examples: pkg/registry/wrapper.go, pkg/model/model/message_test.go, pkg/model/model/conversation_test.go, pkg/model/model/asset_test.go).
🤖 Prompt for AI Agents
In pkg/model/model/planner_event.go around lines 41 to 52, the code currently
concatenates raw p.JobKey (which may contain '#' chars) into PlannerEvent.Key
causing extra '#' segments that break parsers expecting fixed segment counts;
before concatenation, sanitize or encode p.JobKey (e.g., strip any leading '#',
then either escape internal '#' characters or replace with a safe encoding such
as base64 or URL-safe base64) and use that sanitized/encoded value when building
p.Key; after changing this, update expectations in
pkg/model/model/planner_event_test.go and audit/adjust parsers and tests that
split on '#' (examples: pkg/registry/wrapper.go,
pkg/model/model/message_test.go, conversation_test.go, asset_test.go) to accept
the encoded form or decode it as needed.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
pkg/model/model/conversation.go (2)

31-34: Don’t overwrite pre-set defaults.

Only set Created/Source if empty to preserve upstream-provided values.

 func (c *Conversation) Defaulted() {
-	c.Created = Now()
-	c.Source = "user"
+	if c.Created == "" {
+		c.Created = Now()
+	}
+	if c.Source == "" {
+		c.Source = "user"
+	}
 }

51-53: Tighten validation (trim and require Username).

Prevents empty/whitespace-only names and ensures owner is present.

-func (c *Conversation) Valid() bool {
-	return c.Name != ""
-}
+func (c *Conversation) Valid() bool {
+	return strings.TrimSpace(c.Name) != "" && strings.TrimSpace(c.Username) != ""
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f4ad8dd and 7a0db41.

📒 Files selected for processing (2)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/conversation_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/model/model/conversation_test.go
🧰 Additional context used
📓 Path-based instructions (1)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/conversation.go
🧠 Learnings (2)
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Model structs must embed BaseAsset

Applied to files:

  • pkg/model/model/conversation.go
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Where applicable, include neo4j and example struct tags on model fields

Applied to files:

  • pkg/model/model/conversation.go
🧬 Code graph analysis (1)
pkg/model/model/conversation.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (1)
  • Now (242-244)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
🪛 GitHub Actions: Go
pkg/model/model/conversation.go

[error] 1-1: Test suite in package 'github.com/praetorian-inc/tabularium/pkg/model/model' failed. See individual test errors above.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-code-action / claude-code-action
🔇 Additional comments (4)
pkg/model/model/conversation.go (4)

11-16: Add neo4j struct tags (or document intentional omission).

If Conversation participates in graph export, add neo4j:"…" tags matching json names for consistency with peers.

-	Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]"`
-	Key      string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
-	Name     string `dynamodbav:"name" json:"name" desc:"Name of the conversation." example:"My AI Assistant Chat"`
-	UUID     string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
-	Source   string `dynamodbav:"source" json:"source" desc:"Who started the conversation." example:"user"`
-	Created  string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
+	Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]" neo4j:"username"`
+	Key      string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000" neo4j:"key"`
+	Name     string `dynamodbav:"name" json:"name" desc:"Name of the conversation." example:"My AI Assistant Chat" neo4j:"name"`
+	UUID     string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000" neo4j:"uuid"`
+	Source   string `dynamodbav:"source" json:"source" desc:"Who started the conversation." example:"user" neo4j:"source"`
+	Created  string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z" neo4j:"created"`

9-10: Embed BaseAsset instead of registry.BaseModel (repo convention).

Models under pkg/model/{model,attacksurface,beta} must embed BaseAsset. Replace the BaseModel embed accordingly.

 type Conversation struct {
-	registry.BaseModel
+	BaseAsset
 	baseTableModel

39-44: Fix key format to “#conversation##” and slug Name.

Spec/examples use name+uuid; hook currently omits Name. Add simple slugging to avoid “#” and spaces in keys. This likely fixes the failing model tests.

@@
-import (
-	"github.com/google/uuid"
-	"github.com/praetorian-inc/tabularium/pkg/registry"
-)
+import (
+	"strings"
+	"github.com/google/uuid"
+	"github.com/praetorian-inc/tabularium/pkg/registry"
+)
@@
-					c.UUID = conversationID
-					c.Key = "#conversation#" + conversationID
+					c.UUID = conversationID
+					slug := strings.ToLower(strings.TrimSpace(c.Name))
+					slug = strings.NewReplacer("#", "-", " ", "-").Replace(slug)
+					c.Key = "#conversation#" + slug + "#" + conversationID

Also applies to: 3-6


55-63: Propagate hook errors from NewConversation — return an error

  • Change NewConversation signature to (Conversation, error) and return any error from registry.CallHooks.
  • Update callers — current matches are only in tests: pkg/model/model/conversation_test.go (around lines 16, 29, 158, 207).
  • Verify registry.CallHooks actually returns an error; if not, change that API or add an error-returning wrapper before changing the constructor.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

@github-actions
Copy link
Contributor

github-actions bot commented Sep 22, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

- Add tests for new role constants: RoleToolCall, RoleToolResponse, RolePlannerOutput
- Add comprehensive tests for conversation Topic field with truncation
- Verify all new message roles work with NewMessage constructor
- Test topic field behavior with short, long, and empty content
@github-actions
Copy link
Contributor

github-actions bot commented Sep 24, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/model/model/message_test.go (1)

1-399: Switch MessageID generation to KSUID and enforce Username in Valid()

  • In pkg/model/model/message.go, remove github.com/google/uuid import and add github.com/segmentio/ksuid.
  • Replace both uuid.NewV7() calls (around lines 48 and 59) with ksuid.New().String().
  • In func (m *Message) Valid(), add a check if m.Username == "" { return false } so Username is required.
🧹 Nitpick comments (6)
pkg/model/model/planner_event.go (1)

3-13: Prefer adding desc tags to fields for schema parity.

Other models include desc/example tags to drive schema gen. Add brief desc tags here for consistency.

Apply:

 type PlannerEvent struct {
-	Type           string                 `json:"type"`
-	Username       string                 `json:"username"`
-	ConversationID string                 `json:"conversationId"`
+	Type           string                 `json:"type" desc:"Planner event type (e.g., jobCompletion, userMessage)."`
+	Username       string                 `json:"username" desc:"Username associated with the event."`
+	ConversationID string                 `json:"conversationId" desc:"Conversation ID this event relates to."`
@@
-	JobCompletion  *PlannerJobCompletion  `json:"jobCompletion,omitempty"`
+	JobCompletion  *PlannerJobCompletion  `json:"jobCompletion,omitempty" desc:"Details for a job-completion event."`
@@
-	UserMessage    *PlannerUserMessage    `json:"userMessage,omitempty"`
+	UserMessage    *PlannerUserMessage    `json:"userMessage,omitempty" desc:"Details for a user-message event."`
 }
 
 type PlannerJobCompletion struct {
-	JobKey      string   `json:"jobKey"`
-	Source      string   `json:"source"`
-	Target      string   `json:"target"`
-	Status      string   `json:"status"`
-	ResultKeys  []string `json:"resultKeys"`
-	TotalCount  int      `json:"totalCount"`
-	Comment     string   `json:"comment,omitempty"`
-	CompletedAt string   `json:"completedAt"`
+	JobKey      string   `json:"jobKey" desc:"Key of the originating job."`
+	Source      string   `json:"source" desc:"Source label of the job."`
+	Target      string   `json:"target" desc:"Target label of the job."`
+	Status      string   `json:"status" desc:"Final status of the job."`
+	ResultKeys  []string `json:"resultKeys" desc:"Keys of produced results (if any)."`
+	TotalCount  int      `json:"totalCount" desc:"Total count of results/items."`
+	Comment     string   `json:"comment,omitempty" desc:"Optional comment."`
+	CompletedAt string   `json:"completedAt" desc:"Completion timestamp (RFC3339)."`
 }
 
 type PlannerUserMessage struct {
-	Message string `json:"message"`
+	Message string `json:"message" desc:"User-provided message content."`
 }
pkg/model/model/message.go (2)

17-20: Embed BaseAsset per repo convention.

Models under pkg/model/** should embed BaseAsset. Replace BaseModel.

Apply:

 type Message struct {
-	registry.BaseModel
+	BaseAsset
 	baseTableModel

74-84: Optionally propagate hook errors from constructor.

NewMessage ignores hook errors. Consider returning (Message, error) to surface failures.

Example:

func NewMessage(conversationID, role, content, username string) (Message, error) {
    msg := Message{ConversationID: conversationID, Role: role, Content: content, Username: username}
    msg.Defaulted()
    if err := registry.CallHooks(&msg); err != nil {
        return msg, err
    }
    return msg, nil
}
pkg/model/model/conversation.go (3)

3-6: Import strings to safely include Name in Key.

We’ll need strings.ReplaceAll when constructing the key.

Apply:

 import (
 	"github.com/google/uuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
+	"strings"
 )

8-14: Embed BaseAsset per repo convention.

Replace BaseModel embed with BaseAsset.

Apply:

 type Conversation struct {
-	registry.BaseModel
+	BaseAsset
 	baseTableModel

28-35: Optionally include neo4j tags or document omission.

If conversations are exported to graph, add neo4j tags aligned to json names; otherwise add a brief comment stating they’re excluded.

Example:

// Note: Conversation is not exported to Neo4j; neo4j tags intentionally omitted.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac0680 and 607f0fe.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • client/api.yaml (3 hunks)
  • go.mod (1 hunks)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/conversation_test.go (1 hunks)
  • pkg/model/model/message.go (1 hunks)
  • pkg/model/model/message_test.go (1 hunks)
  • pkg/model/model/planner_event.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • go.mod
  • pkg/model/model/conversation_test.go
🧰 Additional context used
📓 Path-based instructions (2)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/message.go
  • pkg/model/model/conversation.go
  • pkg/model/model/message_test.go
  • pkg/model/model/planner_event.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Use the standard Go testing framework (testing package); do not introduce additional test frameworks

Files:

  • pkg/model/model/message_test.go
🧠 Learnings (4)
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Model structs must embed BaseAsset

Applied to files:

  • pkg/model/model/message.go
  • pkg/model/model/conversation.go
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Where applicable, include neo4j and example struct tags on model fields

Applied to files:

  • pkg/model/model/conversation.go
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to **/*_test.go : Use the standard Go testing framework (testing package); do not introduce additional test frameworks

Applied to files:

  • pkg/model/model/message_test.go
📚 Learning: 2025-09-09T14:43:19.609Z
Learnt from: EvanLeleux
PR: praetorian-inc/tabularium#37
File: pkg/model/model/web_application_test.go:75-82
Timestamp: 2025-09-09T14:43:19.609Z
Learning: User EvanLeleux prefers using testify/assert for unit tests in the praetorian-inc/tabularium repository and does not want suggestions to use stdlib testing assertions.

Applied to files:

  • pkg/model/model/message_test.go
🧬 Code graph analysis (4)
pkg/model/model/message.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (2)
  • Now (242-244)
  • Future (246-248)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
pkg/model/model/conversation.go (3)
pkg/registry/registry.go (2)
  • Name (30-36)
  • Registry (11-11)
pkg/model/model/globals.go (1)
  • Now (242-244)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
pkg/model/model/message_test.go (3)
pkg/model/model/message.go (8)
  • NewMessage (74-84)
  • Message (17-30)
  • RoleUser (9-9)
  • RoleChariot (10-10)
  • RoleSystem (11-11)
  • RoleToolCall (12-12)
  • RoleToolResponse (13-13)
  • RolePlannerOutput (14-14)
pkg/model/model/globals.go (1)
  • Future (246-248)
pkg/registry/hook.go (1)
  • CallHooks (10-17)
pkg/model/model/planner_event.go (1)
pkg/model/model/message.go (1)
  • Message (17-30)
🪛 GitHub Actions: Go
pkg/model/model/message.go

[error] Go test suite failed in package: github.com/praetorian-inc/tabularium/pkg/model/model. Process completed with exit code 1.

pkg/model/model/conversation.go

[error] Go test suite failed in package: github.com/praetorian-inc/tabularium/pkg/model/model. Process completed with exit code 1.

pkg/model/model/message_test.go

[error] 61-61: MessageID should be a valid KSUID


[error] 88-88: MessageTest_Hooks: MessageID should be a valid KSUID


[error] 190-190: TestMessage_Valid/missing_username: Not equal: expected false, actual true


[error] 209-209: TestMessage_KSUID_Ordering: KSUIDs not ordered as expected


[error] 273-273: TestMessage_KSUID_Uniqueness: KSUIDs are not unique as expected


[error] 366-366: TestMessage_SecurityScenarios: KSUID validation/security scenarios failed

pkg/model/model/planner_event.go

[error] Go test suite failed in package: github.com/praetorian-inc/tabularium/pkg/model/model. Process completed with exit code 1.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-code-action / claude-code-action
🔇 Additional comments (3)
client/api.yaml (1)

1469-1508: Confirm conversation fields exposed in API.

Model includes Source and Topic fields but schema omits them. Add them or confirm intentional omission.

Would you like Source (who started) and Topic (summary from first message) in the schema for clients?

pkg/model/model/message_test.go (1)

9-12: On using testify in tests.

Previous reviews suggested removing testify, but team preference is to keep it. No action needed.

pkg/model/model/conversation.go (1)

37-45: Fix Key format to include Name segment: “#conversation#name#uuid”.

Matches PR spec and OpenAPI example; sanitize Name to avoid ‘#’ in keys.

Apply:

 func (c *Conversation) GetHooks() []registry.Hook {
 	return []registry.Hook{
 		{
 			Call: func() error {
 				if c.Key == "" {
 					conversationID := uuid.New().String()
 					c.UUID = conversationID
-					c.Key = "#conversation#" + conversationID
+					// sanitize name for key segment
+					safeName := strings.ReplaceAll(c.Name, "#", "-")
+					c.Key = "#conversation#" + safeName + "#" + conversationID
 				}
 				return nil
 			},
 		},
 	}
 }

Comment on lines +2277 to +2326
message:
description: Represents a message within a conversation, with KSUID ordering
for proper sequencing.
properties:
BaseModel:
type: object
content:
description: Content of the message.
example: Hello, how can I help you today?
type: string
conversationId:
description: ID of the conversation this message belongs to.
example: 550e8400-e29b-41d4-a716-446655440000
type: string
key:
description: Unique key for the message.
example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK'
type: string
messageId:
description: KSUID for message ordering.
example: 1sB5tZfLipTVWQWHVKnDFS6kFRK
type: string
role:
description: Role of the message sender (user, chariot, system).
example: user
type: string
timestamp:
description: Timestamp when the message was created (RFC3339).
example: "2023-10-27T10:00:00Z"
type: string
ttl:
description: Time-to-live for the message record (Unix timestamp).
example: "1706353200"
format: int64
type: integer
username:
description: Username who sent the message.
example: [email protected]
type: string
required:
- BaseModel
- content
- conversationId
- key
- messageId
- role
- timestamp
- ttl
- username
type: object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Align message.role description and include tool fields.

Code defines additional roles and tool-use fields; OpenAPI should reflect them.

Apply:

     message:
       description: Represents a message within a conversation, with KSUID ordering
         for proper sequencing.
       properties:
         BaseModel:
           type: object
         content:
           description: Content of the message.
           example: Hello, how can I help you today?
           type: string
         conversationId:
           description: ID of the conversation this message belongs to.
           example: 550e8400-e29b-41d4-a716-446655440000
           type: string
         key:
           description: Unique key for the message.
           example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK'
           type: string
         messageId:
           description: KSUID for message ordering.
           example: 1sB5tZfLipTVWQWHVKnDFS6kFRK
           type: string
         role:
-          description: Role of the message sender (user, chariot, system).
+          description: Role of the message sender (user, chariot, system, tool call, tool response, planner-output).
           example: user
           type: string
         timestamp:
           description: Timestamp when the message was created (RFC3339).
           example: "2023-10-27T10:00:00Z"
           type: string
         ttl:
           description: Time-to-live for the message record (Unix timestamp).
           example: "1706353200"
           format: int64
           type: integer
         username:
           description: Username who sent the message.
           example: [email protected]
           type: string
+        toolUseId:
+          description: Tool use ID for tool result messages.
+          example: tooluse_kZJMlvQmRJ6eAyJE5GIl7Q
+          type: string
+        toolUseContent:
+          description: JSON serialized tool use content for assistant tool use messages.
+          example: '{"name":"query","input":{"node":{"labels":["Asset"]}}}'
+          type: string
       required:
         - BaseModel
         - content
         - conversationId
         - key
         - messageId
         - role
         - timestamp
         - ttl
         - username
+        # toolUseId/toolUseContent are optional
       type: object
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
message:
description: Represents a message within a conversation, with KSUID ordering
for proper sequencing.
properties:
BaseModel:
type: object
content:
description: Content of the message.
example: Hello, how can I help you today?
type: string
conversationId:
description: ID of the conversation this message belongs to.
example: 550e8400-e29b-41d4-a716-446655440000
type: string
key:
description: Unique key for the message.
example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK'
type: string
messageId:
description: KSUID for message ordering.
example: 1sB5tZfLipTVWQWHVKnDFS6kFRK
type: string
role:
description: Role of the message sender (user, chariot, system).
example: user
type: string
timestamp:
description: Timestamp when the message was created (RFC3339).
example: "2023-10-27T10:00:00Z"
type: string
ttl:
description: Time-to-live for the message record (Unix timestamp).
example: "1706353200"
format: int64
type: integer
username:
description: Username who sent the message.
example: [email protected]
type: string
required:
- BaseModel
- content
- conversationId
- key
- messageId
- role
- timestamp
- ttl
- username
type: object
message:
description: Represents a message within a conversation, with KSUID ordering
for proper sequencing.
properties:
BaseModel:
type: object
content:
description: Content of the message.
example: Hello, how can I help you today?
type: string
conversationId:
description: ID of the conversation this message belongs to.
example: 550e8400-e29b-41d4-a716-446655440000
type: string
key:
description: Unique key for the message.
example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK'
type: string
messageId:
description: KSUID for message ordering.
example: 1sB5tZfLipTVWQWHVKnDFS6kFRK
type: string
role:
description: Role of the message sender (user, chariot, system, tool call, tool response, planner-output).
example: user
type: string
timestamp:
description: Timestamp when the message was created (RFC3339).
example: "2023-10-27T10:00:00Z"
type: string
ttl:
description: Time-to-live for the message record (Unix timestamp).
example: "1706353200"
format: int64
type: integer
username:
description: Username who sent the message.
example: [email protected]
type: string
toolUseId:
description: Tool use ID for tool result messages.
example: tooluse_kZJMlvQmRJ6eAyJE5GIl7Q
type: string
toolUseContent:
description: JSON serialized tool use content for assistant tool use messages.
example: '{"name":"query","input":{"node":{"labels":["Asset"]}}}'
type: string
required:
- BaseModel
- content
- conversationId
- key
- messageId
- role
- timestamp
- ttl
- username
# toolUseId/toolUseContent are optional
type: object

Comment on lines +3 to +6
import (
"github.com/google/uuid"
"github.com/praetorian-inc/tabularium/pkg/registry"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use KSUID, not UUIDv7, to generate MessageID. Tests are failing.

MessageID is documented/validated as KSUID. Switch import and ID generation.

Apply:

 import (
-	"github.com/google/uuid"
+	"fmt"
+	"github.com/segmentio/ksuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
 )

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: Go

[error] Go test suite failed in package: github.com/praetorian-inc/tabularium/pkg/model/model. Process completed with exit code 1.

🤖 Prompt for AI Agents
In pkg/model/model/message.go around lines 3 to 6, the code imports
github.com/google/uuid and generates UUIDv7 for MessageID but MessageID must be
a KSUID; update the import to github.com/segmentio/ksuid, replace any uuid-based
ID generation with ksuid.New().String() (or ksuid.NewKSUID().String() as
appropriate), and ensure any tests/validation continue to expect the KSUID
string format.

Comment on lines +44 to +51
func (m *Message) Defaulted() {
m.Timestamp = Now()
m.TTL = Future(24 * 30)
if m.MessageID == "" {
id, _ := uuid.NewV7()
m.MessageID = id.String()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Generate MessageID with ksuid.New() in defaults and hooks.

This aligns with ordering and parsing expectations in tests.

Apply:

 func (m *Message) Defaulted() {
 	m.Timestamp = Now()
 	m.TTL = Future(24 * 30)
 	if m.MessageID == "" {
-		id, _ := uuid.NewV7()
-		m.MessageID = id.String()
+		m.MessageID = ksuid.New().String()
 	}
 }
@@
 			Call: func() error {
 				if m.Key == "" {
-					if m.MessageID == "" {
-						id, _ := uuid.NewV7()
-						m.MessageID = id.String()
-					}
+					if m.ConversationID == "" {
+						return fmt.Errorf("ConversationID is required to build message key")
+					}
+					if m.MessageID == "" {
+						m.MessageID = ksuid.New().String()
+					}
 					m.Key = "#message#" + m.ConversationID + "#" + m.MessageID
 				}
 				return nil
 			},

Also applies to: 58-63

🧰 Tools
🪛 GitHub Actions: Go

[error] Go test suite failed in package: github.com/praetorian-inc/tabularium/pkg/model/model. Process completed with exit code 1.

🤖 Prompt for AI Agents
In pkg/model/model/message.go around lines 44-51 and similarly at 58-63, the
MessageID is generated with uuid.NewV7(); replace those calls to use ksuid.New()
(and assign its String()) so MessageIDs follow the expected ordering/parsing
used in tests, and update imports accordingly (remove uuid import if unused and
add github.com/segmentio/ksuid).

Comment on lines +70 to +72
func (m *Message) Valid() bool {
return m.ConversationID != "" && m.Role != "" && m.Content != ""
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate username in Valid().

Tests expect messages with empty username to be invalid.

Apply:

-func (m *Message) Valid() bool {
-	return m.ConversationID != "" && m.Role != "" && m.Content != ""
-}
+func (m *Message) Valid() bool {
+	return m.ConversationID != "" && m.Role != "" && m.Content != "" && m.Username != ""
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (m *Message) Valid() bool {
return m.ConversationID != "" && m.Role != "" && m.Content != ""
}
func (m *Message) Valid() bool {
return m.ConversationID != "" && m.Role != "" && m.Content != "" && m.Username != ""
}
🧰 Tools
🪛 GitHub Actions: Go

[error] Go test suite failed in package: github.com/praetorian-inc/tabularium/pkg/model/model. Process completed with exit code 1.

🤖 Prompt for AI Agents
In pkg/model/model/message.go around lines 70 to 72, the Valid() method
currently only checks ConversationID, Role, and Content; tests require messages
with an empty Username to be invalid. Update Valid() to also ensure m.Username
!= "" by adding that condition to the boolean expression (i.e., require
ConversationID, Role, Content and Username all be non-empty) so messages with
empty username are treated as invalid.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 24, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

- Fix TestConversation_KeyGeneration_Uniqueness for new key format
- Remove username field references from test cases
- Update hooks test to match new conversation constructor
- Fix security scenarios test structure
- All conversation tests now pass with updated model
@github-actions
Copy link
Contributor

github-actions bot commented Sep 24, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
pkg/model/model/conversation.go (1)

51-53: Strengthen validation (optional).

Validate prefix and UUID format to avoid malformed keys.

 func (c *Conversation) Valid() bool {
-	return c.Key != ""
+	if c.Key == "" {
+		return false
+	}
+	if !strings.HasPrefix(c.Key, "#conversation#") {
+		return false
+	}
+	parts := strings.Split(c.Key, "#")
+	if len(parts) < 3 {
+		return false
+	}
+	last := parts[len(parts)-1]
+	_, err := uuid.Parse(last)
+	return err == nil
 }

Add imports if applying: strings and github.com/google/uuid.

pkg/model/model/conversation_test.go (2)

60-66: Align key parsing with the intended format (“#conversation##”).

Current test assumes 3 parts; with topic included there are 4 parts.

-	keyParts := strings.Split(conv.Key, "#")
-	require.Len(t, keyParts, 3)
-	uuid := keyParts[2]
-	assert.Len(t, uuid, 36)
-	assert.Contains(t, uuid, "-")
+	keyParts := strings.Split(conv.Key, "#")
+	if len(keyParts) != 4 {
+		t.Fatalf("unexpected key format: %q (parts=%d)", conv.Key, len(keyParts))
+	}
+	if keyParts[2] != conv.Topic {
+		t.Fatalf("expected topic segment %q, got %q", conv.Topic, keyParts[2])
+	}
+	uuidPart := keyParts[3]
+	if len(uuidPart) != 36 || !strings.Contains(uuidPart, "-") {
+		t.Fatalf("unexpected UUID segment: %q", uuidPart)
+	}

Note: conv.Topic may be empty in this test; the check remains valid.


45-47: TTL comment is misleading (no TTL is asserted).

Either assert an actual TTL field/behavior or drop the comment.

-	// Verify TTL is approximately 30 days from now
-	assert.NotEmpty(t, conv.Created) // Allow 60 seconds tolerance
+	// Created is set; TTL not asserted here
+	if conv.Created == "" {
+		t.Fatal("expected Created to be set")
+	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 607f0fe and 9a6fc1a.

📒 Files selected for processing (2)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/conversation_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/conversation_test.go
  • pkg/model/model/conversation.go
**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Use the standard Go testing framework (testing package); do not introduce additional test frameworks

Files:

  • pkg/model/model/conversation_test.go
🧠 Learnings (2)
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Model structs must embed BaseAsset

Applied to files:

  • pkg/model/model/conversation.go
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Where applicable, include neo4j and example struct tags on model fields

Applied to files:

  • pkg/model/model/conversation.go
🧬 Code graph analysis (2)
pkg/model/model/conversation_test.go (2)
pkg/model/model/conversation.go (2)
  • NewConversation (55-62)
  • Conversation (8-17)
pkg/registry/hook.go (1)
  • CallHooks (10-17)
pkg/model/model/conversation.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (1)
  • Now (242-244)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
🪛 GitHub Actions: Go
pkg/model/model/conversation_test.go

[error] 103-103: TestConversation_KeyGeneration_Uniqueness failed: Should be true

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-code-action / claude-code-action
🔇 Additional comments (8)
pkg/model/model/conversation.go (6)

11-16: Add neo4j struct tags (where applicable).

Peer models include neo4j tags for graph export. Add matching neo4j tags or document intentional omission.

-	Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]"`
-	Key      string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
-	UUID     string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
-	Source   string `dynamodbav:"source" json:"source" desc:"Who started the conversation." example:"user"`
-	Created  string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
-	Topic    string `dynamodbav:"topic" json:"topic" desc:"Topic extracted from first message (first 256 chars)." example:"Find all active assets in my infrastructure"`
+	Username string `dynamodbav:"username" json:"username" neo4j:"username" desc:"Username who owns the conversation." example:"[email protected]"`
+	Key      string `dynamodbav:"key" json:"key" neo4j:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
+	UUID     string `dynamodbav:"uuid" json:"uuid" neo4j:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
+	Source   string `dynamodbav:"source" json:"source" neo4j:"source" desc:"Who started the conversation." example:"user"`
+	Created  string `dynamodbav:"created" json:"created" neo4j:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
+	Topic    string `dynamodbav:"topic" json:"topic" neo4j:"topic" desc:"Topic extracted from first message (first 256 chars)." example:"Find all active assets in my infrastructure"`

19-21: Registration looks good.

Model is registered via MustRegisterModel in init(), matching project guidelines.


31-34: Sane defaults.

Created and Source defaults are appropriate.


27-29: Description OK.

Matches the intended model semantics.


9-10: Embed BaseAsset (repo convention) instead of registry.BaseModel.

Models under pkg/model/** must embed BaseAsset. Replace registry.BaseModel with BaseAsset to align with conventions (BaseAsset already embeds BaseModel).

 type Conversation struct {
-	registry.BaseModel
+	BaseAsset
 	baseTableModel

39-44: Fix key format to include conversation Topic segment (“#conversation##”).

Spec/tests expect Name/Topic in the key. Current code uses only UUID, which breaks KeyGeneration_Uniqueness and example tags.

 			Call: func() error {
 				if c.Key == "" {
 					conversationID := uuid.New().String()
 					c.UUID = conversationID
-					c.Key = "#conversation#" + conversationID
+					c.Key = "#conversation#" + c.Topic + "#" + conversationID
 				}
 				return nil
 			},

Note: If topics can contain '#', consider normalizing or escaping in a follow-up.

pkg/model/model/conversation_test.go (2)

95-105: Pipeline failure ties to missing Name/Topic in key.

This test expects “#conversation##…”. It will pass after fixing the model hook to include Topic in Key.

After applying the model change, re-run tests to confirm this now passes.


12-24: NewConversation basic assertions are fine.

Once the model embeds Topic in Key, these checks remain valid.

Comment on lines +7 to +10
"github.com/praetorian-inc/tabularium/pkg/registry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Tests must use the standard library (no testify).

Project guideline: “**/*_test.go: Use the standard Go testing framework; do not introduce additional test frameworks.” Replace assert/require with native checks (if/tt.Errorf/tt.Fatal).

Example refactor pattern:

  • assert.Equal(t, got, want) → if got != want { t.Errorf("...") }
  • require.NoError(t, err) → if err != nil { t.Fatal(err) }

Remove these imports:

-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"github.com/praetorian-inc/tabularium/pkg/registry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
"github.com/praetorian-inc/tabularium/pkg/registry"
)
🤖 Prompt for AI Agents
In pkg/model/model/conversation_test.go around lines 7 to 10, the test imports
testify's assert and require which violates the project guideline to use the
standard library for tests; remove "github.com/stretchr/testify/assert" and
"github.com/stretchr/testify/require" from the import block and replace all
assert/require callsites with native testing checks (e.g., assert.Equal -> if
got != want { t.Errorf("... got=%v want=%v", got, want) }; require.NoError -> if
err != nil { t.Fatal(err) } or t.Fatalf with context). Ensure you keep the
"testing" package imported and update all assertions accordingly.

Comment on lines 12 to 16
Key string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
UUID string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
Source string `dynamodbav:"source" json:"source" desc:"Who started the conversation." example:"user"`
Created string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
Topic string `dynamodbav:"topic" json:"topic" desc:"Topic extracted from first message (first 256 chars)." example:"Find all active assets in my infrastructure"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Field naming drift: spec says “name”; code uses Topic. Verify API/schema alignment.

PR summary and examples use “name” while the struct exposes json:"topic". Ensure client/api.yaml and consumers match.


🏁 Script executed:

#!/bin/bash
# Check conversation schema fields in OpenAPI and code
rg -n -C2 --type=yaml '(conversation|Conversation)' client | sed -n '1,200p' || true
rg -n -C2 --type=go '\bConversation\b' pkg/model/model | sed -n '1,200p'
rg -n --type=yaml '\bname:\b' client || true
rg -n --type=yaml '\btopic:\b' client || true

Length of output: 9004


Align struct field to OpenAPI spec
Rename Topic string json:"topic" to Name string json:"name"in pkg/model/model/conversation.go (lines 12–16) so the JSON field matches thename` property defined in client/api.yaml.

🤖 Prompt for AI Agents
In pkg/model/model/conversation.go around lines 12 to 16, the struct field Topic
must be renamed to Name and its JSON tag changed from `json:"topic"` to
`json:"name"` to match the OpenAPI spec; update the Go field identifier to Name,
change the struct tags (dynamodbav and json) accordingly, adjust the
desc/example text to refer to the conversation name (first 256 chars) and ensure
any code, tests or DynamoDB mappings that reference Conversation.Topic are
updated to use Conversation.Name to keep compile-time correctness and consistent
DB serialization.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 24, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
pkg/model/model/conversation.go (3)

3-6: Add strings import (used by slug/validation).

Needed for key format validation and name slugging.

Apply this diff:

 import (
 	"github.com/google/uuid"
 	"github.com/praetorian-inc/tabularium/pkg/registry"
+	"strings"
 )

31-33: Don’t overwrite Created if already set.

Only default Created when empty to preserve upstream timestamps.

Apply this diff:

 func (c *Conversation) Defaulted() {
-	c.Created = Now()
+	if c.Created == "" {
+		c.Created = Now()
+	}
 }

50-52: Strengthen validation (check UUID and key prefix).

Basic non-empty check is too weak for persisted keys.

Apply this diff:

 func (c *Conversation) Valid() bool {
-	return c.Key != ""
+	return c.Key != "" &&
+		c.UUID != "" &&
+		strings.HasPrefix(c.Key, "#conversation#")
 }
pkg/model/model/planner_event.go (4)

3-16: Add desc (and example) struct tags for schema quality.

Improves API docs and consistency with other types.

Apply this diff:

 type PlannerEvent struct {
-	Type string `json:"type"`
+	Type string `json:"type" desc:"Event type (e.g., user_message, job_completion)." example:"user_message"`
 	// Username of the tenant we're in
-	Username string `json:"username"`
+	Username string `json:"username" desc:"Tenant username context." example:"[email protected]"`
 	// User that requested the job, possibly assumed in to the tenant
-	User           string `json:"user"`
-	ConversationID string `json:"conversationId"`
+	User           string `json:"user" desc:"User who initiated the action (may be impersonated)." example:"[email protected]"`
+	ConversationID string `json:"conversationId" desc:"Conversation UUID associated with the event." example:"550e8400-e29b-41d4-a716-446655440000"`
 
 	// Job completion event
-	JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty"`
+	JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty" desc:"Job completion payload if type=job_completion."`
 
 	// User message event
-	UserMessage *PlannerUserMessage `json:"userMessage,omitempty"`
+	UserMessage *PlannerUserMessage `json:"userMessage,omitempty" desc:"User message payload if type=user_message."`
 }

18-27: Document PlannerJobCompletion fields for clarity and clients.

Adds desc/example tags for downstream consumers.

Apply this diff:

 type PlannerJobCompletion struct {
-	JobKey      string   `json:"jobKey"`
-	Source      string   `json:"source"`
-	Target      string   `json:"target"`
-	Status      string   `json:"status"`
-	ResultKeys  []string `json:"resultKeys"`
-	TotalCount  int      `json:"totalCount"`
-	Comment     string   `json:"comment,omitempty"`
-	CompletedAt string   `json:"completedAt"`
+	JobKey      string   `json:"jobKey" desc:"Key of the completed job." example:"#job#example.com#550e8400-e29b-41d4-a716-446655440000"`
+	Source      string   `json:"source" desc:"Source system producing the results." example:"scanner"`
+	Target      string   `json:"target" desc:"Target entity of the job." example:"example.com"`
+	Status      string   `json:"status" desc:"Completion status." example:"success"`
+	ResultKeys  []string `json:"resultKeys" desc:"Result asset keys produced by the job." example:"[#asset#example.com#A, #asset#example.com#B]"`
+	TotalCount  int      `json:"totalCount" desc:"Total number of results produced." example:"42"`
+	Comment     string   `json:"comment,omitempty" desc:"Optional human-readable comment." example:"Completed with minor warnings"`
+	CompletedAt string   `json:"completedAt" desc:"Completion timestamp (RFC3339 UTC)." example:"2025-09-15T12:34:56Z"`
 }

29-31: Document PlannerUserMessage.message.

Minor schema polish.

Apply this diff:

 type PlannerUserMessage struct {
-	Message string `json:"message"`
+	Message string `json:"message" desc:"User-entered message content." example:"Show open ports on example.com"`
 }

3-5: Optional: define typed constants for event types/status.

Reduces stringly-typed errors across services.

Add these near the top of the file:

const (
	PlannerEventTypeUserMessage   = "user_message"
	PlannerEventTypeJobCompletion = "job_completion"

	PlannerStatusSuccess = "success"
	PlannerStatusFailed  = "failed"
)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ec5885a and 4e893f4.

📒 Files selected for processing (3)
  • pkg/model/model/conversation.go (1 hunks)
  • pkg/model/model/job.go (1 hunks)
  • pkg/model/model/planner_event.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/model/model/job.go
🧰 Additional context used
📓 Path-based instructions (1)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/planner_event.go
  • pkg/model/model/conversation.go
🧠 Learnings (2)
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Model structs must embed BaseAsset

Applied to files:

  • pkg/model/model/conversation.go
📚 Learning: 2025-08-30T00:29:52.572Z
Learnt from: CR
PR: praetorian-inc/tabularium#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:29:52.572Z
Learning: Applies to pkg/model/{model,attacksurface,beta}/**/*.go : Where applicable, include neo4j and example struct tags on model fields

Applied to files:

  • pkg/model/model/conversation.go
🧬 Code graph analysis (2)
pkg/model/model/planner_event.go (1)
pkg/model/model/message.go (1)
  • Message (17-30)
pkg/model/model/conversation.go (3)
pkg/registry/registry.go (1)
  • Registry (11-11)
pkg/model/model/globals.go (1)
  • Now (242-244)
pkg/registry/hook.go (2)
  • Hook (4-7)
  • CallHooks (10-17)
🔇 Additional comments (6)
pkg/model/model/conversation.go (5)

19-21: Registration looks good.

Model is registered via init using registry.MustRegisterModel.

Please confirm no duplicate registrations exist for Conversation across the repo.


8-10: Embed BaseAsset instead of registry.BaseModel (project requirement).

Models under pkg/model/** must embed BaseAsset. Replace registry.BaseModel with BaseAsset to follow conventions and avoid schema/behavior drift.

Apply this diff:

 type Conversation struct {
-	registry.BaseModel
+	BaseAsset
 	baseTableModel

11-16: Align field name with API: Topic → Name; add neo4j tags.

PR spec and OpenAPI describe “name”; current struct exposes json:"topic". Also add neo4j tags per guidelines.

Apply this diff:

-	Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]"`
-	Key      string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"`
-	UUID     string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"`
-	User     string `dynamodbav:"user" json:"user" desc:"Who started the conversation." example:"[email protected]"`
-	Created  string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"`
-	Topic    string `dynamodbav:"topic" json:"topic" desc:"Topic extracted from first message (first 256 chars)." example:"Find all active assets in my infrastructure"`
+	Username string `dynamodbav:"username" json:"username" desc:"Tenant username that owns the conversation." example:"[email protected]" neo4j:"username"`
+	Key      string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000" neo4j:"key"`
+	UUID     string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000" neo4j:"uuid"`
+	User     string `dynamodbav:"user" json:"user" desc:"Who started the conversation." example:"[email protected]" neo4j:"user"`
+	Created  string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z" neo4j:"created"`
+	Name     string `dynamodbav:"name" json:"name" desc:"Human-friendly conversation name (first 256 chars of first message)." example:"Find all active assets in my infrastructure" neo4j:"name"`

39-43: Fix Key format to include name: "#conversation##".

Spec/tests expect the name segment. Also sanitize the name to avoid raw '#'.

Apply this diff:

 				if c.Key == "" {
 					conversationID := uuid.New().String()
 					c.UUID = conversationID
-					c.Key = "#conversation#" + conversationID
+					nameSeg := slugName(c.Name)
+					if nameSeg == "" {
+						nameSeg = "unnamed"
+					}
+					c.Key = "#conversation#" + nameSeg + "#" + conversationID
 				}

Add this helper in the file (outside the shown range):

// slugName normalizes a conversation name for safe key composition.
func slugName(name string) string {
	s := strings.TrimSpace(name)
	s = strings.ReplaceAll(s, "#", "-")
	s = strings.Join(strings.Fields(s), "-")
	s = strings.ToLower(s)
	if len(s) > 128 {
		s = s[:128]
	}
	return s
}

54-61: Rename constructor param to name and set Name field. Update callers.

Matches schema and updated struct.

Apply this diff:

-func NewConversation(topic string) Conversation {
+func NewConversation(name string) Conversation {
 	conv := Conversation{
-		Topic: topic,
+		Name: name,
 	}
 	conv.Defaulted()
 	registry.CallHooks(&conv)
 	return conv
 }

Run to find and update references:

#!/bin/bash
# Find usages to update Topic -> Name and constructor callers.
rg -n -C2 --type=go '\bConversation\b.*\bTopic\b|json:"topic"|dynamodbav:"topic"' pkg client || true
rg -n -C2 --type=go '\bNewConversation\(' || true
rg -n -C2 --type=yaml '\bconversation\b.*\b(topic|name)\b' client || true
pkg/model/model/planner_event.go (1)

1-2: Confirm if PlannerEvent types are API payloads (not registry models).

If not persisted/registered, it’s fine to omit BaseAsset/registration. If they should appear in generated schema/docs, add desc/example tags; if they’re models, they must embed BaseAsset and be registered.

Would you like me to add schema tags or model wrappers for these?

- Replace ResultKeys []string with Results []map[string]interface{}
- Enable rich job completion context with full object data
- Support first 20 complete objects instead of just keys
@github-actions
Copy link
Contributor

github-actions bot commented Sep 25, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e893f4 and 4041da2.

📒 Files selected for processing (1)
  • pkg/model/model/planner_event.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
pkg/model/{model,attacksurface,beta}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

pkg/model/{model,attacksurface,beta}/**/*.go: Register every new model in its init() using registry.Registry.MustRegisterModel(&ModelName{})
Model structs must embed BaseAsset
All model fields must include json and desc struct tags for proper schema generation
Where applicable, include neo4j and example struct tags on model fields

Files:

  • pkg/model/model/planner_event.go
🧬 Code graph analysis (1)
pkg/model/model/planner_event.go (1)
pkg/model/model/message.go (1)
  • Message (17-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-code-action / claude-code-action

Comment on lines 3 to 31
type PlannerEvent struct {
Type string `json:"type"`
// Username of the tenant we're in
Username string `json:"username"`
// User that requested the job, possibly assumed in to the tenant
User string `json:"user"`
ConversationID string `json:"conversationId"`

// Job completion event
JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty"`

// User message event
UserMessage *PlannerUserMessage `json:"userMessage,omitempty"`
}

type PlannerJobCompletion struct {
JobKey string `json:"jobKey"`
Source string `json:"source"`
Target string `json:"target"`
Status string `json:"status"`
Results []map[string]interface{} `json:"results"`
TotalCount int `json:"totalCount"`
Comment string `json:"comment,omitempty"`
CompletedAt string `json:"completedAt"`
}

type PlannerUserMessage struct {
Message string `json:"message"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add the required desc struct tags on every field.

Per the model guidelines, each field must carry a desc tag for schema generation. None of the fields in PlannerEvent, PlannerJobCompletion, or PlannerUserMessage have it, so the OpenAPI output will drop those descriptions. Please add appropriate descriptions (and examples if you have them).

 type PlannerEvent struct {
-	Type string `json:"type"`
+	Type string `json:"type" desc:"Planner event type (e.g., jobCompletion, userMessage)."`
 	// Username of the tenant we're in
-	Username string `json:"username"`
+	Username string `json:"username" desc:"Tenant username in whose context the event occurred."`
 	// User that requested the job, possibly assumed in to the tenant
-	User           string `json:"user"`
-	ConversationID string `json:"conversationId"`
+	User           string `json:"user" desc:"User who initiated the planner action."`
+	ConversationID string `json:"conversationId" desc:"Conversation identifier associated with this planner event."`
 
 	// Job completion event
-	JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty"`
+	JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty" desc:"Payload describing a planner job completion event."`
 
 	// User message event
-	UserMessage *PlannerUserMessage `json:"userMessage,omitempty"`
+	UserMessage *PlannerUserMessage `json:"userMessage,omitempty" desc:"Payload containing a user-authored message for the planner."`
 }
 
 type PlannerJobCompletion struct {
-	JobKey      string                   `json:"jobKey"`
-	Source      string                   `json:"source"`
-	Target      string                   `json:"target"`
-	Status      string                   `json:"status"`
-	Results     []map[string]interface{} `json:"results"`
-	TotalCount  int                      `json:"totalCount"`
-	Comment     string                   `json:"comment,omitempty"`
-	CompletedAt string                   `json:"completedAt"`
+	JobKey      string                   `json:"jobKey" desc:"Full job key associated with this completion."`
+	Source      string                   `json:"source" desc:"Source entity for the planner job."`
+	Target      string                   `json:"target" desc:"Target entity for the planner job."`
+	Status      string                   `json:"status" desc:"Final status of the planner job."`
+	Results     []map[string]interface{} `json:"results" desc:"Structured results returned by the planner job."`
+	TotalCount  int                      `json:"totalCount" desc:"Number of result records returned by the planner job."`
+	Comment     string                   `json:"comment,omitempty" desc:"Optional planner-supplied comment about the job."`
+	CompletedAt string                   `json:"completedAt" desc:"RFC3339 timestamp when the job completed."`
 }
 
 type PlannerUserMessage struct {
-	Message string `json:"message"`
+	Message string `json:"message" desc:"Raw message content sent by the user to the planner."`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type PlannerEvent struct {
Type string `json:"type"`
// Username of the tenant we're in
Username string `json:"username"`
// User that requested the job, possibly assumed in to the tenant
User string `json:"user"`
ConversationID string `json:"conversationId"`
// Job completion event
JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty"`
// User message event
UserMessage *PlannerUserMessage `json:"userMessage,omitempty"`
}
type PlannerJobCompletion struct {
JobKey string `json:"jobKey"`
Source string `json:"source"`
Target string `json:"target"`
Status string `json:"status"`
Results []map[string]interface{} `json:"results"`
TotalCount int `json:"totalCount"`
Comment string `json:"comment,omitempty"`
CompletedAt string `json:"completedAt"`
}
type PlannerUserMessage struct {
Message string `json:"message"`
}
type PlannerEvent struct {
Type string `json:"type" desc:"Planner event type (e.g., jobCompletion, userMessage)."`
// Username of the tenant we're in
Username string `json:"username" desc:"Tenant username in whose context the event occurred."`
// User that requested the job, possibly assumed in to the tenant
User string `json:"user" desc:"User who initiated the planner action."`
ConversationID string `json:"conversationId" desc:"Conversation identifier associated with this planner event."`
// Job completion event
JobCompletion *PlannerJobCompletion `json:"jobCompletion,omitempty" desc:"Payload describing a planner job completion event."`
// User message event
UserMessage *PlannerUserMessage `json:"userMessage,omitempty" desc:"Payload containing a user-authored message for the planner."`
}
type PlannerJobCompletion struct {
JobKey string `json:"jobKey" desc:"Full job key associated with this completion."`
Source string `json:"source" desc:"Source entity for the planner job."`
Target string `json:"target" desc:"Target entity for the planner job."`
Status string `json:"status" desc:"Final status of the planner job."`
Results []map[string]interface{} `json:"results" desc:"Structured results returned by the planner job."`
TotalCount int `json:"totalCount" desc:"Number of result records returned by the planner job."`
Comment string `json:"comment,omitempty" desc:"Optional planner-supplied comment about the job."`
CompletedAt string `json:"completedAt" desc:"RFC3339 timestamp when the job completed."`
}
type PlannerUserMessage struct {
Message string `json:"message" desc:"Raw message content sent by the user to the planner."`
}

- Add ResultKeys field back alongside Results for complete key list
- Collect all result keys for comprehensive entity reference
- Include first 20 full objects plus up to 50 keys in summary prompt
- Remove intelligent extraction, provide raw full objects to AI
- Enable AI to analyze both detailed objects and complete key inventory
@github-actions
Copy link
Contributor

github-actions bot commented Sep 25, 2025

Claude finished @noah-tutt-praetorian's task —— View job


PR Review Analysis

Todo List

  • Analyze changed files for language types
  • Assess significance of changes
  • Verify presence of automated tests
  • Review code quality and patterns
  • Provide structured findings and recommendations

Has Go, TypeScript, or Python Changes: Yes
Has Significant Changes: Yes
Has Automated Tests: Yes

Findings

KSUID Dependencies: Added github.com/segmentio/ksuid dependency for chronological message ordering
DynamoDB Integration: Both models properly extend baseTableModel with appropriate dynamodbav tags
Key Pattern Compliance: Follows documented patterns #conversation#name#uuid and #message#conversationId#ksuid
Registry Integration: Both models correctly registered via registry.Registry.MustRegisterModel() in init() functions
Comprehensive Testing: 219 lines of conversation tests and 387 lines of message tests with security scenarios
Hook Implementation: Both models implement proper hooks for key generation with existing key preservation
KSUID Ordering: Message tests validate chronological ordering and uniqueness of KSUID-based MessageIDs

Recommendations

Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system"
Key Length Concerns: Conversation keys with user-provided names could become very long - consider length limits
Security Review Needed: Test cases include SQL injection/XSS scenarios but treat them as "valid" - ensure proper sanitization at application layer

- Remove explanatory comments from planner service files
- Fix tool iteration count back to 10 from 25
- Clean up code for production readiness
@github-actions
Copy link
Contributor

github-actions bot commented Sep 25, 2025

Claude finished @noah-tutt-praetorian's task —— View job


I'll analyze this and get back to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants