Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion crates/ironclaw_product_workflow/src/reborn_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,15 @@ impl RebornServicesApi for RebornServices {
caller: WebUiAuthenticatedCaller,
query: RebornOperatorLogsQuery,
) -> Result<RebornOperatorCommandPlaneResponse, RebornServicesError> {
if query.tail && query.follow {
return Err(RebornServicesError::validation(
WebUiInboundValidationError::new(
"follow",
WebUiInboundValidationCode::InvalidValue,
),
));
}

let request = bounded_operator_logs_query(query);
let logs = self.operator_logs.query_logs(caller, request).await?;
Ok(RebornOperatorCommandPlaneResponse {
Expand Down Expand Up @@ -4899,7 +4908,8 @@ fn bounded_operator_logs_query(query: RebornOperatorLogsQuery) -> RebornLogQuery
tool_call_id: bounded_operator_logs_context_string(query.tool_call_id),
tool_name: bounded_operator_logs_context_string(query.tool_name),
source: bounded_operator_logs_context_string(query.source),
tail: false,
tail: query.tail,
follow: query.follow,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
think-in-universe marked this conversation as resolved.
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/ironclaw_product_workflow/src/reborn_services/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ pub struct RebornLogQueryRequest {
pub source: Option<String>,
#[serde(default)]
pub tail: bool,
#[serde(default)]
pub follow: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -1361,6 +1363,8 @@ pub struct RebornOperatorLogsQuery {
pub source: Option<String>,
#[serde(default)]
pub tail: bool,
#[serde(default)]
pub follow: bool,
Comment thread
think-in-universe marked this conversation as resolved.
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down
85 changes: 82 additions & 3 deletions crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5845,6 +5845,7 @@ async fn query_operator_logs_bounds_query_before_logs_service() {
tool_name: Some("shell".to_string()),
source: Some(boundary_source),
tail: true,
follow: false,
},
)
.await
Expand All @@ -5870,10 +5871,88 @@ async fn query_operator_logs_bounds_query_before_logs_service() {
assert!(run_id.ends_with(" ... [truncated]"));
assert!(run_id.is_char_boundary(run_id.len()));
assert_eq!(requests[0].level, Some(RebornLogLevel::Warn));
assert!(
!requests[0].tail,
"unsupported streaming must not reach the logs backend"
assert!(requests[0].tail);
assert!(!requests[0].follow);
}

#[tokio::test]
async fn query_operator_logs_forwards_follow_mode_to_logs_service() {
let operator_logs = Arc::new(RecordingOperatorLogsService::default());
let services = RebornServices::new(
Arc::new(InMemorySessionThreadService::default()),
Arc::new(FakeTurnCoordinator::default()),
)
.with_operator_logs_service(operator_logs.clone());

services
.query_operator_logs(
caller(),
RebornOperatorLogsQuery {
limit: Some(25),
cursor: Some("after:7".to_string()),
level: Some(RebornLogLevel::Info),
target: Some("ironclaw".to_string()),
thread_id: None,
run_id: None,
turn_id: None,
tool_call_id: None,
tool_name: None,
source: None,
tail: false,
follow: true,
},
)
.await
.expect("operator logs follow query");

let requests = operator_logs.requests();
assert_eq!(requests.len(), 1);
assert_eq!(requests[0].limit, Some(25));
assert_eq!(requests[0].cursor.as_deref(), Some("after:7"));
assert_eq!(requests[0].level, Some(RebornLogLevel::Info));
assert_eq!(requests[0].target.as_deref(), Some("ironclaw"));
assert!(!requests[0].tail);
assert!(requests[0].follow);
}

#[tokio::test]
async fn query_operator_logs_rejects_ambiguous_tail_follow_modes() {
let operator_logs = Arc::new(RecordingOperatorLogsService::default());
let services = RebornServices::new(
Arc::new(InMemorySessionThreadService::default()),
Arc::new(FakeTurnCoordinator::default()),
)
.with_operator_logs_service(operator_logs.clone());

let err = services
.query_operator_logs(
caller(),
RebornOperatorLogsQuery {
limit: None,
cursor: None,
level: None,
target: None,
thread_id: None,
run_id: None,
turn_id: None,
tool_call_id: None,
tool_name: None,
source: None,
tail: true,
follow: true,
},
)
.await
.expect_err("tail and follow cannot be combined");

assert_eq!(err.kind, RebornServicesErrorKind::Validation);
assert_eq!(err.status_code, 400);
assert_eq!(err.field.as_deref(), Some("follow"));
assert_eq!(
err.validation_code,
Some(WebUiInboundValidationCode::InvalidValue)
);
assert!(operator_logs.requests().is_empty());
}

#[tokio::test]
Expand Down
2 changes: 1 addition & 1 deletion crates/ironclaw_reborn_composition/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ rows are inventoried here, not implemented in the current PR.
| Extensions registry/list/install/activate/remove/setup | `GET\|POST /api/extensions/*` | `GET /api/webchat/v2/extensions`, `GET /api/webchat/v2/extensions/registry`, `POST /api/webchat/v2/extensions/install`, `POST /api/webchat/v2/extensions/{package_id}/{activate,remove,setup}` | Mapped to lifecycle package refs and registry projections; setup projects credential requirements and product-auth OAuth start is mounted under the extension setup surface |
| LLM provider config | v1 settings/provider config surface | `GET /api/webchat/v2/llm/providers`, `POST /api/webchat/v2/llm/providers`, `POST /api/webchat/v2/llm/providers/{provider_id}/delete`, `POST /api/webchat/v2/llm/active`, `POST /api/webchat/v2/llm/{test-connection,list-models}` | Mapped for trusted operator-token deployments; left unmounted for multi-user authenticators until an admin role boundary exists |
| Operator status/readiness | v1 doctor/readiness surfaces | `GET /api/webchat/v2/operator/status` | Mapped to Reborn readiness projection through the product facade; left unmounted with other operator routes for multi-user authenticators |
| Operator logs | `src/cli/logs.rs` command path | `GET /api/webchat/v2/operator/logs` | Route and facade shell mapped; default runtime returns unavailable until a log backend is wired |
| Operator logs | `src/cli/logs.rs` command path | `GET /api/webchat/v2/operator/logs` | Mapped to the in-process operator log buffer with bounded query, tail, follow, filter, cursor, and redaction behavior |
| Operator service lifecycle | `src/cli/service.rs` command path | `POST /api/webchat/v2/operator/service` | Route and facade shell mapped; default runtime reports unsupported until a platform lifecycle backend is wired |
| SSO login (Google) | `GET /auth/providers`, `GET /auth/login/{p}`, `GET /auth/callback/{p}`, `POST /auth/logout` | Same paths on the v2 listener via `ironclaw_reborn_webui_ingress::webui_v2_auth_router`, merged into `webui_v2_app` through [`WebuiServeConfig::with_public_route_mount`] (typed `{ router, descriptors }` so the per-route body-limit / rate-limit middleware applies) | Mapped (Google); GitHub + NEAR follow under #4116 |

Expand Down
Loading
Loading