feat(agents): introduce agent lifecycle domain and proto#480
Conversation
yordis
commented
Jul 13, 2026
- Establishes a single authoritative contract for the agent lifecycle so provisioning, revision staging, verdicts, activation, rollback, and archival cannot drift between the wire format and the code that enforces the rules.
- Models each concept as a validated value object so invalid agent state is unrepresentable at the boundary rather than surfacing late at runtime.
PR SummaryLow Risk Overview The bulk of new material is It also adds fifteen new product dossiers under Reviewed by Cursor Bugbot for commit e80608a. Bugbot is set up for automated code reviews on this repo. Configure here. |
Model the agent lifecycle as validated value objects and events so provisioning, revision staging, verdicts, activation, rollback, and archival share one authoritative contract across proto and the decider. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
af1cb66 to
f0a0d07
Compare
WalkthroughThis change adds an agent-platform research corpus and decision record, defines protobuf contracts for agent revisions and lifecycle events, introduces a validated Rust agent domain with six event-driven deciders, and adds codec, WASM simulation, feature-gating, and lifecycle integration tests. ChangesAgent platform research
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Preserve the competitive research and rationale behind the agent-platform direction so future changes can be weighed against the evidence that shaped it. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (9)
rsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rs (1)
127-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
lifecycle()helper across deciders. Both files define a byte-for-byte identical helper (state lookup →as_known()fallback → UNSPECIFIED rejection), differing only in the generated proto type names; the shared root cause is the lack of a common lifecycle-extraction abstraction forstate_v1::*::Lifecycleenums.
rsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rs#L127-L145: extract this logic into a shared generic helper (e.g. a small macro or trait over the state/Lifecycletype) inevent_fold.rsand delegate to it here.rsworkspace/crates/trogonai-agents-domain/src/commands/activate_revision.rs#L241-L259: delegate to the same shared helper instead of re-defining an identical function.This pattern likely recurs in the other decider files in this PR (
provision_agent.rs,stage_revision.rs,record_revision_verdict.rs,rollback_revision.rs), which aren't in the current review batch — worth confirming and extending the extraction to those as well.♻️ Example extraction sketch
// event_fold.rs pub(crate) fn known_lifecycle<L>( lifecycle: &Option<buffa::EnumValue<L>>, ) -> Result<L, AgentEventFoldError> where L: Copy + PartialEq + buffa::EnumFullName, // adjust bound to whatever buffa exposes { let value = lifecycle.as_ref().ok_or(AgentEventFoldError::MissingStateField("lifecycle"))?; let known = value.as_known().ok_or(AgentEventFoldError::UnknownStateValue { field: "lifecycle", value: value.to_i32(), })?; Ok(known) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rs` around lines 127 - 145, The lifecycle extraction logic is duplicated across deciders; add a shared generic helper in rsworkspace/crates/trogonai-agents-domain/src/event_fold.rs and preserve missing-field, unknown-value, and UNSPECIFIED rejection behavior. Update lifecycle() in rsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rs (lines 127-145) and rsworkspace/crates/trogonai-agents-domain/src/commands/activate_revision.rs (lines 241-259) to delegate to it, and inspect provision_agent.rs, stage_revision.rs, record_revision_verdict.rs, and rollback_revision.rs for the same pattern, extending the shared helper usage where present.rsworkspace/crates/trogonai-agents-domain/src/commands/stage_revision/tests.rs (1)
59-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding a test for change-class overstatement.
The existing test covers understatement (declared
LearnedLayerbut requiredCharter). Thedecidefunction rejects both directions via the same!=check, but a test for the overstatement direction (declaredCharterbut requiredLearnedLayer) would document that explicitly.🧪 Suggested overstatement test
+ +#[test] +fn rejects_a_change_class_that_overstates_the_change() { + let mut command = stage_command(revision_two(), ChangeClass::LearnedLayer); + command.change_class = ChangeClass::Charter; + + TestCase::<StageRevision>::new() + .given([provisioned_event()]) + .when(command) + .then_error(StageRevisionError::ChangeClassMismatch { + declared: ChangeClass::Charter, + required: ChangeClass::LearnedLayer, + }); +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/stage_revision/tests.rs` around lines 59 - 71, Add a test alongside rejects_a_change_class_that_understates_the_change covering overstatement: use a revision requiring ChangeClass::LearnedLayer, declare ChangeClass::Charter, and assert StageRevisionError::ChangeClassMismatch with declared Charter and required LearnedLayer.rsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors/tests.rs (1)
3-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd tests for
InvalidRequiredandInvalidOptionalerror variants.The test covers the happy path and
Overlap, butToolSelectorsError::InvalidRequiredandToolSelectorsError::InvalidOptional(blank/untrimmed selectors) are untested.✅ Suggested additional test cases
#[test] fn validates_ordered_disjoint_tool_selectors() { let selectors = ToolSelectors::new( BTreeSet::from(["bash".to_string()]), BTreeSet::from(["web_search".to_string()]), ) .unwrap(); assert!(selectors.required().contains("bash")); assert!(matches!( ToolSelectors::new( BTreeSet::from(["bash".to_string()]), BTreeSet::from(["bash".to_string()]) ), Err(ToolSelectorsError::Overlap { .. }) )); } + +#[test] +fn rejects_blank_required_selector() { + assert!(matches!( + ToolSelectors::new( + BTreeSet::from(["".to_string()]), + BTreeSet::new(), + ), + Err(ToolSelectorsError::InvalidRequired { .. }) + )); +} + +#[test] +fn rejects_blank_optional_selector() { + assert!(matches!( + ToolSelectors::new( + BTreeSet::new(), + BTreeSet::from(["".to_string()]), + ), + Err(ToolSelectorsError::InvalidOptional { .. }) + )); +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors/tests.rs` around lines 3 - 18, Add test cases in validates_ordered_disjoint_tool_selectors for ToolSelectors::new returning ToolSelectorsError::InvalidRequired when a required selector is blank or untrimmed, and InvalidOptional for the corresponding optional selector inputs. Assert the specific error variants while preserving the existing valid and Overlap coverage.rsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors.rs (1)
5-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a
ToolSelectorvalue object to align with domain modeling guidelines.
ToolSelectorsstores individual selector names as bareStringprimitives and validates nonblank/trimmed at the aggregate level. The coding guidelines require preferring domain-specific value objects over primitives and validating per-type, not per-aggregate. AToolSelectorvalue object would move the nonblank/trimmed check to its ownnewconstructor, leavingToolSelectors::newresponsible only for the overlap constraint. This also eliminatesInvalidRequired/InvalidOptionalfromToolSelectorsErrorand preserves the originalNonblankErrorcontext instead of discarding it via.is_err().As per coding guidelines: "Prefer domain-specific value objects over primitives (e.g.,
AcpPrefixinstead ofString)" and "Validate per-type, not per-aggregate: avoid validating unrelated fields together in a single constructor."♻️ Proposed refactor sketch
use std::collections::BTreeSet; -use super::nonblank::validate_nonblank; +use super::nonblank::validate_nonblank; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ToolSelector(String); + +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum ToolSelectorError { + #[error("tool selector '{selector}' must be nonblank and trimmed")] + Invalid { selector: String }, +} + +impl ToolSelector { + pub fn new(selector: String) -> Result<Self, ToolSelectorError> { + validate_nonblank(&selector).map_err(|_| ToolSelectorError::Invalid { selector })?; + Ok(Self(selector)) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct ToolSelectors { - required: BTreeSet<String>, - optional: BTreeSet<String>, + required: BTreeSet<ToolSelector>, + optional: BTreeSet<ToolSelector>, } #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum ToolSelectorsError { - #[error("required tool selector '{selector}' must be nonblank and trimmed")] - InvalidRequired { selector: String }, - #[error("optional tool selector '{selector}' must be nonblank and trimmed")] - InvalidOptional { selector: String }, #[error("tool selector '{selector}' cannot be both required and optional")] - Overlap { selector: String }, + Overlap { selector: ToolSelector }, } impl ToolSelectors { - pub fn new(required: BTreeSet<String>, optional: BTreeSet<String>) -> Result<Self, ToolSelectorsError> { - for selector in &required { - if validate_nonblank(selector).is_err() { - return Err(ToolSelectorsError::InvalidRequired { - selector: selector.clone(), - }); - } - } - for selector in &optional { - if validate_nonblank(selector).is_err() { - return Err(ToolSelectorsError::InvalidOptional { - selector: selector.clone(), - }); - } - } + pub fn new(required: BTreeSet<ToolSelector>, optional: BTreeSet<ToolSelector>) -> Result<Self, ToolSelectorsError> { if let Some(selector) = required.intersection(&optional).next() { return Err(ToolSelectorsError::Overlap { selector: selector.clone(), }); } Ok(Self { required, optional }) } - pub fn required(&self) -> &BTreeSet<String> { + pub fn required(&self) -> &BTreeSet<ToolSelector> { &self.required } - pub fn optional(&self) -> &BTreeSet<String> { + pub fn optional(&self) -> &BTreeSet<ToolSelector> { &self.optional } }Also applies to: 22-43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors.rs` around lines 5 - 8, Introduce a domain-specific ToolSelector value object with a new constructor that validates and preserves the existing NonblankError for nonblank, trimmed selector names. Update ToolSelectors.required and optional to store ToolSelector values, and adjust construction/accessors as needed. Make ToolSelectors::new enforce only the required/optional overlap constraint, removing InvalidRequired and InvalidOptional from ToolSelectorsError and propagating ToolSelector construction errors unchanged.Source: Coding guidelines
rsworkspace/crates/trogonai-agents-domain/src/commands/domain/delegate_selectors.rs (1)
5-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a selector value object instead of raw
String
nonblank.rsonly providesvalidate_nonblank, soDelegateSelectorsandToolSelectorsstill validate individual strings themselves. A dedicated selector type would make invalid selectors unrepresentable and leave these aggregates to enforce only overlap.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/delegate_selectors.rs` around lines 5 - 9, Introduce a dedicated selector value object validated through the existing nonblank validation, and replace the raw String element types in DelegateSelectors’ required and optional BTreeSets with that type. Update ToolSelectors and related construction/access paths consistently so individual selector validation is centralized in the value object, while the aggregate types enforce only required/optional overlap rules.Source: Coding guidelines
rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rollback_reason.rs (1)
3-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider dedicated value types for
metric/window/deltainstead of rawString.Each field is independently validated for nonblank inside a single shared constructor rather than owning its own factory/type. As per coding guidelines: "Prefer domain-specific value objects over primitives (e.g.,
AcpPrefixinstead ofString)" and "Validate per-type, not per-aggregate: avoid validating unrelated fields together in a single constructor." Other value objects in this PR (SkillRef,TemplateId,TenantId) follow the per-type pattern;RollbackReasoncurrently doesn't.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rollback_reason.rs` around lines 3 - 32, Introduce dedicated domain value types for the metric, window, and delta fields, each owning its nonblank-and-trimmed validation and exposing its own constructor or factory. Update RollbackReason and its constructor to accept and store those value types, removing the per-field primitive validation from RollbackReason while preserving the existing InvalidMetric, InvalidWindow, and InvalidDelta error behavior through the respective type boundaries.Source: Coding guidelines
rsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict_reasons.rs (1)
9-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider including the raw input in
VerdictReasonsErrorfor diagnostic parity.
TenantIdErrorcaptures the raw input (raw: String) for better error messages, butVerdictReasonsErroronly stores theNonBlankViolation. Adding the raw value would make error messages more actionable and maintain consistency across domain error types.♻️ Proposed refactor to include raw input
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] -#[error("verdict reasons are invalid: {violation}")] +#[error("verdict reasons '{raw}' are invalid: {violation}")] pub struct VerdictReasonsError { + raw: String, violation: NonBlankViolation, }Then update the
parsemethod:pub fn parse(raw: &str) -> Result<Self, VerdictReasonsError> { - validate_nonblank(raw).map_err(|violation| VerdictReasonsError { violation })?; + validate_nonblank(raw).map_err(|violation| VerdictReasonsError { + raw: raw.to_string(), + violation, + })?; Ok(Self(raw.to_string())) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict_reasons.rs` around lines 9 - 13, Update VerdictReasonsError and the VerdictReasons::parse flow to retain the original raw input alongside NonBlankViolation, following the TenantIdError pattern. Include the raw value in the error display while preserving the existing validation behavior and violation details.rsworkspace/crates/trogonai-agents-domain/src/commands/domain/evidence/tests.rs (2)
22-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider asserting
outcome_refsordering alongsidesession_refs.The test verifies
session_refsare deterministically ordered but doesn't assertoutcome_refscontent or ordering, leaving that path unverified.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/evidence/tests.rs` around lines 22 - 40, Extend stores_references_deterministically to also inspect evidence.outcome_refs(), mapping each OutcomeRef with as_str and asserting the collected values equal the expected deterministic order, such as ["outcome-1"].
5-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider adding test cases for whitespace violation and both-invalid scenario.
The test covers the empty-rationale and empty-refs cases separately, but doesn't test:
SurroundingWhitespaceviolation (e.g.," reason "should also returnInvalidRationale).- The both-invalid case (empty refs + empty rationale) to document which error takes priority.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/evidence/tests.rs` around lines 5 - 19, Extend requires_nonblank_rationale_and_at_least_one_typed_ref to assert that whitespace-surrounded rationale such as " reason " returns EvidenceError::InvalidRationale, and add a case with both empty references and empty rationale to document the constructor’s error precedence. Use Evidence::new and the existing error variants, preserving the current separate validation cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/archive_reason.rs`:
- Around line 9-18: Update ArchiveReasonError and ArchiveReason::parse to retain
the invalid raw input as a String and include it in the error display, matching
the raw-capturing behavior of ModelIdError, SessionRefError, and AgentIdError
while preserving the existing violation details.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_fields.rs`:
- Around line 17-39: Move the immutable-field validation from classify() into
new(), returning ChangedFieldsError::ImmutableFields for any immutable entries
after the empty-set check. Make classify() infallible while preserving its
Charter versus LearnedLayer classification, and update changed_fields tests so
construction asserts the immutable error and classification calls no longer
unwrap a Result.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_fields/tests.rs`:
- Around line 21-32: Update the test around ChangedFields::new to assert that
construction directly returns Err(ChangedFieldsError::ImmutableFields { fields:
BTreeSet::from([ChangedField::Name, ChangedField::Runtime]) }). Remove the
unwrap and classify() error assertion, while preserving the existing
immutable-field set.
In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/nonblank.rs`:
- Around line 3-7: Add a std::error::Error implementation for the
NonBlankViolation enum in nonblank.rs, preserving its existing Display behavior
and variants.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_result/tests.rs`:
- Around line 5-13: Update the RubricResult construction test to assert the
baseline score through result.baseline().get(), expecting 0.7, alongside the
existing rubric, candidate, and trials assertions.
In `@rsworkspace/crates/trogonai-proto/src/agents/agents/codec/tests.rs`:
- Around line 183-199: The test
event_decode_dispatches_all_lifecycle_event_types overstates coverage. Either
rename it to reflect that it only dispatches AgentProvisioned and AgentArchived,
or extend its EventDecode::decode assertions to include RevisionStaged,
RevisionVerdictRecorded, RevisionActivated, and RevisionRolledBack, preserving
the existing variant-matching style.
---
Nitpick comments:
In `@rsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rs`:
- Around line 127-145: The lifecycle extraction logic is duplicated across
deciders; add a shared generic helper in
rsworkspace/crates/trogonai-agents-domain/src/event_fold.rs and preserve
missing-field, unknown-value, and UNSPECIFIED rejection behavior. Update
lifecycle() in
rsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rs (lines
127-145) and
rsworkspace/crates/trogonai-agents-domain/src/commands/activate_revision.rs
(lines 241-259) to delegate to it, and inspect provision_agent.rs,
stage_revision.rs, record_revision_verdict.rs, and rollback_revision.rs for the
same pattern, extending the shared helper usage where present.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/delegate_selectors.rs`:
- Around line 5-9: Introduce a dedicated selector value object validated through
the existing nonblank validation, and replace the raw String element types in
DelegateSelectors’ required and optional BTreeSets with that type. Update
ToolSelectors and related construction/access paths consistently so individual
selector validation is centralized in the value object, while the aggregate
types enforce only required/optional overlap rules.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/evidence/tests.rs`:
- Around line 22-40: Extend stores_references_deterministically to also inspect
evidence.outcome_refs(), mapping each OutcomeRef with as_str and asserting the
collected values equal the expected deterministic order, such as ["outcome-1"].
- Around line 5-19: Extend
requires_nonblank_rationale_and_at_least_one_typed_ref to assert that
whitespace-surrounded rationale such as " reason " returns
EvidenceError::InvalidRationale, and add a case with both empty references and
empty rationale to document the constructor’s error precedence. Use
Evidence::new and the existing error variants, preserving the current separate
validation cases.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rollback_reason.rs`:
- Around line 3-32: Introduce dedicated domain value types for the metric,
window, and delta fields, each owning its nonblank-and-trimmed validation and
exposing its own constructor or factory. Update RollbackReason and its
constructor to accept and store those value types, removing the per-field
primitive validation from RollbackReason while preserving the existing
InvalidMetric, InvalidWindow, and InvalidDelta error behavior through the
respective type boundaries.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors.rs`:
- Around line 5-8: Introduce a domain-specific ToolSelector value object with a
new constructor that validates and preserves the existing NonblankError for
nonblank, trimmed selector names. Update ToolSelectors.required and optional to
store ToolSelector values, and adjust construction/accessors as needed. Make
ToolSelectors::new enforce only the required/optional overlap constraint,
removing InvalidRequired and InvalidOptional from ToolSelectorsError and
propagating ToolSelector construction errors unchanged.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors/tests.rs`:
- Around line 3-18: Add test cases in validates_ordered_disjoint_tool_selectors
for ToolSelectors::new returning ToolSelectorsError::InvalidRequired when a
required selector is blank or untrimmed, and InvalidOptional for the
corresponding optional selector inputs. Assert the specific error variants while
preserving the existing valid and Overlap coverage.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict_reasons.rs`:
- Around line 9-13: Update VerdictReasonsError and the VerdictReasons::parse
flow to retain the original raw input alongside NonBlankViolation, following the
TenantIdError pattern. Include the raw value in the error display while
preserving the existing validation behavior and violation details.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/stage_revision/tests.rs`:
- Around line 59-71: Add a test alongside
rejects_a_change_class_that_understates_the_change covering overstatement: use a
revision requiring ChangeClass::LearnedLayer, declare ChangeClass::Charter, and
assert StageRevisionError::ChangeClassMismatch with declared Charter and
required LearnedLayer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f267a30d-9645-43ae-8f8f-17c80be9afad
⛔ Files ignored due to path filters (46)
rsworkspace/Cargo.lockis excluded by!**/*.lockrsworkspace/crates/trogonai-proto/src/gen/mod.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.activate_revision_state.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.activate_revision_state.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.archive_agent_state.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.archive_agent_state.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.mod.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.provision_agent_state.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.provision_agent_state.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.record_revision_verdict_state.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.record_revision_verdict_state.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.rollback_revision_state.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.rollback_revision_state.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.stage_revision_state.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.state.v1.stage_revision_state.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.activate_revision.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.activate_revision.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_archived.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_archived.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.agent_provisioned.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.archive_agent.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.archive_agent.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__oneof.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.__view_oneof.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.events.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.mod.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.provision_agent.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.provision_agent.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.record_revision_verdict.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.record_revision_verdict.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_activated.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_activated.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_rolled_back.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_rolled_back.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_staged.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_staged.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_verdict_recorded.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.revision_verdict_recorded.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.rollback_revision.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.rollback_revision.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.stage_revision.__view.rsis excluded by!**/gen/**rsworkspace/crates/trogonai-proto/src/gen/trogonai.agents.agents.v1.stage_revision.rsis excluded by!**/gen/**
📒 Files selected for processing (137)
docs/architecture/agent-platform/research/decision-record.mddocs/architecture/agent-platform/research/index.mddocs/architecture/agent-platform/research/products/adk-a2a.mddocs/architecture/agent-platform/research/products/bedrock-agentcore.mddocs/architecture/agent-platform/research/products/claude-code-agent-sdk.mddocs/architecture/agent-platform/research/products/claude-managed-agents.mddocs/architecture/agent-platform/research/products/cloudflare-agents.mddocs/architecture/agent-platform/research/products/crewai.mddocs/architecture/agent-platform/research/products/devin.mddocs/architecture/agent-platform/research/products/hermes-agent.mddocs/architecture/agent-platform/research/products/jido.mddocs/architecture/agent-platform/research/products/langgraph-platform.mddocs/architecture/agent-platform/research/products/netclaw.mddocs/architecture/agent-platform/research/products/openai-agents-sdk.mddocs/architecture/agent-platform/research/products/openclaw.mddocs/architecture/agent-platform/research/products/opencomputer.mddocs/architecture/agent-platform/research/products/vercel.mddocs/architecture/agent-platform/research/synthesis.mdproto/trogonai/agents/agents/state/v1/activate_revision_state.protoproto/trogonai/agents/agents/state/v1/archive_agent_state.protoproto/trogonai/agents/agents/state/v1/provision_agent_state.protoproto/trogonai/agents/agents/state/v1/record_revision_verdict_state.protoproto/trogonai/agents/agents/state/v1/rollback_revision_state.protoproto/trogonai/agents/agents/state/v1/stage_revision_state.protoproto/trogonai/agents/agents/v1/activate_revision.protoproto/trogonai/agents/agents/v1/agent.protoproto/trogonai/agents/agents/v1/agent_archived.protoproto/trogonai/agents/agents/v1/agent_provisioned.protoproto/trogonai/agents/agents/v1/archive_agent.protoproto/trogonai/agents/agents/v1/events.protoproto/trogonai/agents/agents/v1/provision_agent.protoproto/trogonai/agents/agents/v1/record_revision_verdict.protoproto/trogonai/agents/agents/v1/revision_activated.protoproto/trogonai/agents/agents/v1/revision_rolled_back.protoproto/trogonai/agents/agents/v1/revision_staged.protoproto/trogonai/agents/agents/v1/revision_verdict_recorded.protoproto/trogonai/agents/agents/v1/rollback_revision.protoproto/trogonai/agents/agents/v1/stage_revision.protorsworkspace/crates/trogon-decider-sim/Cargo.tomlrsworkspace/crates/trogon-decider-sim/src/fixture.rsrsworkspace/crates/trogon-decider-sim/tests/agents.rsrsworkspace/crates/trogonai-agents-domain/Cargo.tomlrsworkspace/crates/trogonai-agents-domain/src/commands/activate_revision.rsrsworkspace/crates/trogonai-agents-domain/src/commands/activate_revision/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent.rsrsworkspace/crates/trogonai-agents-domain/src/commands/archive_agent/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_charter.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_charter/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_definition.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_definition/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_id.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_id/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_name.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/agent_name/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/annotations.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/annotations/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/archive_reason.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/archive_reason/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/change_class.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/change_class/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/change_summary.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/change_summary/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_field.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_field/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_fields.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_fields/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/content_digest.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/content_digest/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/delegate_selectors.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/delegate_selectors/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/evidence.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/evidence/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/kubernetes_syntax.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/labels.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/labels/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/mod.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/model_id.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/model_id/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/model_parameters.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/model_parameters/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/nonblank.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/outcome_ref.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/outcome_ref/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/parent_ref.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/parent_ref/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/principal.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/principal/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/revision_number.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/revision_number/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rollback_reason.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rollback_reason/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_ref.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_ref/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_result.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_result/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_score.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_score/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/runtime_id.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/runtime_id/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/session_ref.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/session_ref/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/skill_ref.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/skill_ref/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/template_id.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/template_id/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/tenant_id.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/tenant_id/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/tool_selectors/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/trial_count.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/trial_count/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict_reasons.rsrsworkspace/crates/trogonai-agents-domain/src/commands/domain/verdict_reasons/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/event_fold.rsrsworkspace/crates/trogonai-agents-domain/src/commands/lifecycle_tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/mod.rsrsworkspace/crates/trogonai-agents-domain/src/commands/proto_wire.rsrsworkspace/crates/trogonai-agents-domain/src/commands/proto_wire/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/provision_agent.rsrsworkspace/crates/trogonai-agents-domain/src/commands/provision_agent/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/record_revision_verdict.rsrsworkspace/crates/trogonai-agents-domain/src/commands/record_revision_verdict/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/rollback_revision.rsrsworkspace/crates/trogonai-agents-domain/src/commands/rollback_revision/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/stage_revision.rsrsworkspace/crates/trogonai-agents-domain/src/commands/stage_revision/tests.rsrsworkspace/crates/trogonai-agents-domain/src/commands/test_support.rsrsworkspace/crates/trogonai-agents-domain/src/lib.rsrsworkspace/crates/trogonai-proto/Cargo.tomlrsworkspace/crates/trogonai-proto/src/agents/agents/codec.rsrsworkspace/crates/trogonai-proto/src/agents/agents/codec/tests.rsrsworkspace/crates/trogonai-proto/src/agents/agents/mod.rsrsworkspace/crates/trogonai-proto/src/agents/mod.rsrsworkspace/crates/trogonai-proto/src/lib.rsrsworkspace/crates/trogonai-proto/src/tests.rs
| #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] | ||
| #[error("archive reason is invalid: {violation}")] | ||
| pub struct ArchiveReasonError { | ||
| violation: NonBlankViolation, | ||
| } | ||
|
|
||
| impl ArchiveReason { | ||
| pub fn parse(raw: &str) -> Result<Self, ArchiveReasonError> { | ||
| validate_nonblank(raw).map_err(|violation| ArchiveReasonError { violation })?; | ||
| Ok(Self(raw.to_string())) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
ArchiveReasonError loses the raw input — inconsistent with sibling error types.
ModelIdError, SessionRefError, and AgentIdError all store raw: String and include it in their Display output (e.g., "model id '' is invalid: must not be empty"). ArchiveReasonError omits the raw value, producing "archive reason is invalid: must not be empty" — making it harder to identify the offending input during debugging.
🔧 Proposed fix to capture raw input
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
-#[error("archive reason is invalid: {violation}")]
+#[error("archive reason '{raw}' is invalid: {violation}")]
pub struct ArchiveReasonError {
+ raw: String,
violation: NonBlankViolation,
}
impl ArchiveReason {
pub fn parse(raw: &str) -> Result<Self, ArchiveReasonError> {
- validate_nonblank(raw).map_err(|violation| ArchiveReasonError { violation })?;
+ validate_nonblank(raw).map_err(|violation| ArchiveReasonError {
+ raw: raw.to_string(),
+ violation,
+ })?;
Ok(Self(raw.to_string()))
}📝 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.
| #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] | |
| #[error("archive reason is invalid: {violation}")] | |
| pub struct ArchiveReasonError { | |
| violation: NonBlankViolation, | |
| } | |
| impl ArchiveReason { | |
| pub fn parse(raw: &str) -> Result<Self, ArchiveReasonError> { | |
| validate_nonblank(raw).map_err(|violation| ArchiveReasonError { violation })?; | |
| Ok(Self(raw.to_string())) | |
| #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] | |
| #[error("archive reason '{raw}' is invalid: {violation}")] | |
| pub struct ArchiveReasonError { | |
| raw: String, | |
| violation: NonBlankViolation, | |
| } | |
| impl ArchiveReason { | |
| pub fn parse(raw: &str) -> Result<Self, ArchiveReasonError> { | |
| validate_nonblank(raw).map_err(|violation| ArchiveReasonError { | |
| raw: raw.to_string(), | |
| violation, | |
| })?; | |
| Ok(Self(raw.to_string())) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/archive_reason.rs`
around lines 9 - 18, Update ArchiveReasonError and ArchiveReason::parse to
retain the invalid raw input as a String and include it in the error display,
matching the raw-capturing behavior of ModelIdError, SessionRefError, and
AgentIdError while preserving the existing violation details.
| pub fn new(fields: BTreeSet<ChangedField>) -> Result<Self, ChangedFieldsError> { | ||
| if fields.is_empty() { | ||
| return Err(ChangedFieldsError::Empty); | ||
| } | ||
| Ok(Self(fields)) | ||
| } | ||
|
|
||
| pub fn classify(&self) -> Result<ChangeClass, ChangedFieldsError> { | ||
| let immutable = self | ||
| .0 | ||
| .iter() | ||
| .copied() | ||
| .filter(|field| field.is_immutable()) | ||
| .collect::<BTreeSet<_>>(); | ||
| if !immutable.is_empty() { | ||
| return Err(ChangedFieldsError::ImmutableFields { fields: immutable }); | ||
| } | ||
| if self.0.iter().copied().any(ChangedField::requires_charter_review) { | ||
| Ok(ChangeClass::Charter) | ||
| } else { | ||
| Ok(ChangeClass::LearnedLayer) | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
new() must reject immutable fields at construction, not defer to classify().
Per the coding guidelines, "Each type's factory must guarantee correctness at construction—invalid instances should be unrepresentable" and "After conversion from boundary types to domain types, domain values must be valid forever." Currently new() only checks for emptiness, allowing a ChangedFields containing immutable fields to exist as a valid-looking domain object. The immutable check only happens later in classify(), violating the invariant that domain values are valid forever after construction.
Move the immutable-field validation into new() and make classify() infallible:
🔧 Proposed fix
impl ChangedFields {
pub fn new(fields: BTreeSet<ChangedField>) -> Result<Self, ChangedFieldsError> {
if fields.is_empty() {
return Err(ChangedFieldsError::Empty);
}
- Ok(Self(fields))
+ let immutable = fields
+ .iter()
+ .copied()
+ .filter(|field| field.is_immutable())
+ .collect::<BTreeSet<_>>();
+ if !immutable.is_empty() {
+ return Err(ChangedFieldsError::ImmutableFields { fields: immutable });
+ }
+ Ok(Self(fields))
}
- pub fn classify(&self) -> Result<ChangeClass, ChangedFieldsError> {
- let immutable = self
- .0
- .iter()
- .copied()
- .filter(|field| field.is_immutable())
- .collect::<BTreeSet<_>>();
- if !immutable.is_empty() {
- return Err(ChangedFieldsError::ImmutableFields { fields: immutable });
- }
+ pub fn classify(&self) -> ChangeClass {
if self.0.iter().copied().any(ChangedField::requires_charter_review) {
- Ok(ChangeClass::Charter)
+ ChangeClass::Charter
} else {
- Ok(ChangeClass::LearnedLayer)
+ ChangeClass::LearnedLayer
}
}The test in changed_fields/tests.rs (lines 21–32) will also need updating: new() should now return Err(ChangedFieldsError::ImmutableFields { ... }) directly, and classify() calls should no longer unwrap a Result.
📝 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.
| pub fn new(fields: BTreeSet<ChangedField>) -> Result<Self, ChangedFieldsError> { | |
| if fields.is_empty() { | |
| return Err(ChangedFieldsError::Empty); | |
| } | |
| Ok(Self(fields)) | |
| } | |
| pub fn classify(&self) -> Result<ChangeClass, ChangedFieldsError> { | |
| let immutable = self | |
| .0 | |
| .iter() | |
| .copied() | |
| .filter(|field| field.is_immutable()) | |
| .collect::<BTreeSet<_>>(); | |
| if !immutable.is_empty() { | |
| return Err(ChangedFieldsError::ImmutableFields { fields: immutable }); | |
| } | |
| if self.0.iter().copied().any(ChangedField::requires_charter_review) { | |
| Ok(ChangeClass::Charter) | |
| } else { | |
| Ok(ChangeClass::LearnedLayer) | |
| } | |
| } | |
| pub fn new(fields: BTreeSet<ChangedField>) -> Result<Self, ChangedFieldsError> { | |
| if fields.is_empty() { | |
| return Err(ChangedFieldsError::Empty); | |
| } | |
| let immutable = fields | |
| .iter() | |
| .copied() | |
| .filter(|field| field.is_immutable()) | |
| .collect::<BTreeSet<_>>(); | |
| if !immutable.is_empty() { | |
| return Err(ChangedFieldsError::ImmutableFields { fields: immutable }); | |
| } | |
| Ok(Self(fields)) | |
| } | |
| pub fn classify(&self) -> ChangeClass { | |
| if self.0.iter().copied().any(ChangedField::requires_charter_review) { | |
| ChangeClass::Charter | |
| } else { | |
| ChangeClass::LearnedLayer | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_fields.rs`
around lines 17 - 39, Move the immutable-field validation from classify() into
new(), returning ChangedFieldsError::ImmutableFields for any immutable entries
after the empty-set check. Make classify() infallible while preserving its
Charter versus LearnedLayer classification, and update changed_fields tests so
construction asserts the immutable error and classification calls no longer
unwrap a Result.
Source: Coding guidelines
| let fields = ChangedFields::new(BTreeSet::from([ | ||
| ChangedField::Instructions, | ||
| ChangedField::Runtime, | ||
| ChangedField::Name, | ||
| ])) | ||
| .unwrap(); | ||
| assert_eq!( | ||
| fields.classify(), | ||
| Err(ChangedFieldsError::ImmutableFields { | ||
| fields: BTreeSet::from([ChangedField::Name, ChangedField::Runtime]) | ||
| }) | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
This test will need updating once new() rejects immutable fields.
This test currently relies on new() accepting immutable fields (via .unwrap()) and classify() catching them later. Once new() is fixed to reject immutable fields at construction (as flagged on changed_fields.rs), this test should assert that new() itself returns Err(ChangedFieldsError::ImmutableFields { ... }) and remove the classify() error assertion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/changed_fields/tests.rs`
around lines 21 - 32, Update the test around ChangedFields::new to assert that
construction directly returns Err(ChangedFieldsError::ImmutableFields { fields:
BTreeSet::from([ChangedField::Name, ChangedField::Runtime]) }). Remove the
unwrap and classify() error assertion, while preserving the existing
immutable-field set.
Source: Coding guidelines
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub(crate) enum NonBlankViolation { | ||
| Empty, | ||
| SurroundingWhitespace, | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
NonBlankViolation must implement std::error::Error.
NonBlankViolation is returned as the Err variant from validate_nonblank, making it an error type. Per coding guidelines, every error type must implement both Display and std::error::Error. It implements Display but is missing std::error::Error.
As per coding guidelines: "Errors must be typed—use structs or enums, never String or format!(). Every error type must implement Display and std::error::Error."
🔧 Proposed fix: add `std::error::Error` impl
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NonBlankViolation {
Empty,
SurroundingWhitespace,
}
+impl std::error::Error for NonBlankViolation {}
+
impl fmt::Display for NonBlankViolation {📝 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.
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| pub(crate) enum NonBlankViolation { | |
| Empty, | |
| SurroundingWhitespace, | |
| } | |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| pub(crate) enum NonBlankViolation { | |
| Empty, | |
| SurroundingWhitespace, | |
| } | |
| impl std::error::Error for NonBlankViolation {} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/nonblank.rs`
around lines 3 - 7, Add a std::error::Error implementation for the
NonBlankViolation enum in nonblank.rs, preserving its existing Display behavior
and variants.
Source: Coding guidelines
| let result = RubricResult::new( | ||
| RubricRef::parse("review@2").unwrap(), | ||
| RubricScore::new(0.7).unwrap(), | ||
| RubricScore::new(0.9).unwrap(), | ||
| TrialCount::new(6).unwrap(), | ||
| ); | ||
| assert_eq!(result.rubric().as_str(), "review@2"); | ||
| assert_eq!(result.candidate().get(), 0.9); | ||
| assert_eq!(result.trials().get(), 6); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify baseline accessor exists and parent includes mod tests
rg -n 'pub fn baseline' rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_result.rs
rg -n 'mod tests' rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_result.rsRepository: TrogonStack/trogonai
Length of output: 172
Assert the baseline score too. RubricResult::new takes a baseline value, but this test never checks it. Add assert_eq!(result.baseline().get(), 0.7); alongside the existing rubric, candidate, and trials assertions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@rsworkspace/crates/trogonai-agents-domain/src/commands/domain/rubric_result/tests.rs`
around lines 5 - 13, Update the RubricResult construction test to assert the
baseline score through result.baseline().get(), expecting 0.7, alongside the
existing rubric, candidate, and trials assertions.
Source: Learnings
| #[test] | ||
| fn event_decode_dispatches_all_lifecycle_event_types() { | ||
| let agent_archived = v1::AgentArchived { | ||
| agent_id: "agent-1".to_string(), | ||
| reason: "superseded".to_string(), | ||
| principal: "user:alice".to_string(), | ||
| }; | ||
|
|
||
| let decoded = <v1::AgentEvent as EventDecode>::decode(EventData::new( | ||
| <v1::AgentArchived as buffa::MessageName>::FULL_NAME, | ||
| &agent_archived.encode_to_vec(), | ||
| )) | ||
| .unwrap() | ||
| .into_decoded() | ||
| .unwrap(); | ||
| assert!(matches!(decoded.event, Some(AgentEventCase::AgentArchived(_)))); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
event_decode_dispatches_all_lifecycle_event_types overstates its scope
EventDecode::decode is only asserted for AgentProvisioned and AgentArchived; the other lifecycle variants are only covered through encode/decode_from_slice. Rename the test or add dispatch assertions for RevisionStaged, RevisionVerdictRecorded, RevisionActivated, and RevisionRolledBack.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@rsworkspace/crates/trogonai-proto/src/agents/agents/codec/tests.rs` around
lines 183 - 199, The test event_decode_dispatches_all_lifecycle_event_types
overstates coverage. Either rename it to reflect that it only dispatches
AgentProvisioned and AgentArchived, or extend its EventDecode::decode assertions
to include RevisionStaged, RevisionVerdictRecorded, RevisionActivated, and
RevisionRolledBack, preserving the existing variant-matching style.