-
Notifications
You must be signed in to change notification settings - Fork 0
Add conversations/messages models for running capabilities #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- 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]>
WalkthroughAdds 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)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Comment |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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 sortableActually, 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.
⛔ Files ignored due to path filters (1)
go.sumis 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.gopkg/model/model/conversation_test.gopkg/model/model/message_test.gopkg/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.gopkg/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/ksuiddependency 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.BaseModelandbaseTableModelas required by the coding guidelines. All fields include the necessarydynamodbav,json, anddescstruct tags for proper schema generation.
21-23: Model registration implemented correctly.The model is properly registered in
init()usingregistry.Registry.MustRegisterModel(&Conversation{})as required by the coding guidelines.
57-65: Constructor implementation is clean and follows the pattern.The
NewConversationfunction 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
NewConversationfunction 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
NewMessagefunction 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]>
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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.
📒 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.gopkg/model/model/message_test.gopkg/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.
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| "github.com/segmentio/ksuid" | ||
| "github.com/praetorian-inc/tabularium/pkg/registry" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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().
| 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"` | ||
| } |
There was a problem hiding this comment.
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/modelLength 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.
| 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 | ||
| }, | ||
| }, | ||
| } | ||
| } |
There was a problem hiding this comment.
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]>
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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.
📒 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.gopkg/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.gopkg/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 tagspkg/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.
pkg/model/model/conversation.go
Outdated
| 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"` |
There was a problem hiding this comment.
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 50Length 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' || trueLength 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.
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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-timemodules/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.
📒 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.gopkg/model/model/job_test.gopkg/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.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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))) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.BuilderCommittable 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.
| 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) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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: uuidNote: 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.
📒 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.
pkg/model/model/planner_event.go
Outdated
| func (p *PlannerEvent) GetHooks() []registry.Hook { | ||
| return []registry.Hook{ | ||
| { | ||
| Call: func() error { | ||
| if p.Key == "" { | ||
| p.Key = "#plannerevent#" + p.ConversationID + "#" + p.JobKey | ||
| } | ||
| return nil | ||
| }, | ||
| }, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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' || trueLength 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.
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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.
📒 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 + "#" + conversationIDAlso 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.
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
- 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
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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/uuidimport and addgithub.com/segmentio/ksuid.- Replace both
uuid.NewV7()calls (around lines 48 and 59) withksuid.New().String().- In
func (m *Message) Valid(), add a checkif 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.
⛔ Files ignored due to path filters (1)
go.sumis 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.gopkg/model/model/conversation.gopkg/model/model/message_test.gopkg/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.gopkg/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 }, }, } }
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 |
| import ( | ||
| "github.com/google/uuid" | ||
| "github.com/praetorian-inc/tabularium/pkg/registry" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| func (m *Message) Defaulted() { | ||
| m.Timestamp = Now() | ||
| m.TTL = Future(24 * 30) | ||
| if m.MessageID == "" { | ||
| id, _ := uuid.NewV7() | ||
| m.MessageID = id.String() | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
| func (m *Message) Valid() bool { | ||
| return m.ConversationID != "" && m.Role != "" && m.Content != "" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
- 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
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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.
📒 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.gopkg/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.
| "github.com/praetorian-inc/tabularium/pkg/registry" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) |
There was a problem hiding this comment.
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.
| "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.
| 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"` |
There was a problem hiding this comment.
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 || trueLength 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.
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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.
📒 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.gopkg/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 || truepkg/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
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
There was a problem hiding this 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.
📒 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
| 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"` | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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
|
Claude finished @noah-tutt-praetorian's task —— View job PR Review AnalysisTodo List
Has Go, TypeScript, or Python Changes: Yes Findings• KSUID Dependencies: Added Recommendations• Consider Role Validation: Message model accepts any role value - consider enum/validation for "user", "assistant", "system" |
- Remove explanatory comments from planner service files - Fix tool iteration count back to 10 from 25 - Clean up code for production readiness
|
Claude finished @noah-tutt-praetorian's task —— View job I'll analyze this and get back to you. |
Summary
Key Features
#conversation#name#uuidand#message#conversationId#ksuidTest Coverage
🤖 Generated with Claude Code