Synced from monorepo
Synced from monorepo Changes: - grok-shell: send an expired external-provider credential to the sign-in flow, not a 401 loop - pager: clickable ▲ jumps to the top of the response being read - grok-shell: keep a large task log from making the completion message too long - Plan viewer scrollbar: widen grab zone to the border column; fix striped thumb in Terminal.app - pager: poll the tmux probe teardown grace instead of sleeping it - security: vendor-compat MCP kill switch is now actually enforced when reported as on - grok-shell: restore session eviction when a leader client disconnects - Bump rust-toolchain to 1.93.0 - workspace: lexical-normalize permission path patterns before glob matching - pager: reject garbage Enter in the /resume picker - pager: show Mermaid affordances in plan mode preview - pager: drop manage-account link from /session-info - workspace: auto-approve read-only git queries; defer write floor to auto classifier - Add free-form pattern editor to the "Always allow" command prompt - grok-shell: fix /btw caching - pager: Tab walks answers in the ask_user_question card - External-provider auth refresh: single 7s attempt instead of 3×5s - pager: don't resurrect finished background tasks as Running when completion arrives first - pager: report tmux truecolor clamping in Doctor - Fix plan viewer scrollbar click+drag hijacked by comment gutter - pager/shell: stop double Recap after the same last turn - sampler: preserve x-should-retry through stream collection - pager: clear plan-mode indicator immediately when the user approves a plan - pager: tmux does not re-read its config on reattach Source-Revision: 64c4de99cc822b25ce9c54ab5a4f372093d0885d
This commit is contained in:
parent
a422116582
commit
780d1388ff
323 changed files with 12258 additions and 7226 deletions
|
|
@ -0,0 +1,107 @@
|
|||
//! HTTPS (TLS) gRPC transport coverage for the external OTEL stream —
|
||||
//! regression test for GB-4580, where `https://` collector endpoints were
|
||||
//! rejected at exporter build time and the stream silently disabled itself.
|
||||
//!
|
||||
//! The collector presents a certificate signed by a freshly generated CA and
|
||||
//! the client trusts it via the standard `OTEL_EXPORTER_OTLP_CERTIFICATE`
|
||||
//! variable, so the full TLS handshake + OTLP export path is exercised.
|
||||
//! Lives in its own integration-test binary because the external telemetry
|
||||
//! registry is a process-global `OnceLock`.
|
||||
|
||||
mod otlp_collector;
|
||||
|
||||
use otlp_collector as col;
|
||||
|
||||
#[test]
|
||||
fn external_stream_grpc_over_tls_end_to_end() {
|
||||
let tls = col::generate_tls_material();
|
||||
let ca_file = tempfile::NamedTempFile::new().expect("CA temp file");
|
||||
std::fs::write(ca_file.path(), &tls.ca_cert_pem).expect("write CA pem");
|
||||
let ca_path = ca_file.path().to_str().expect("utf-8 CA path").to_string();
|
||||
|
||||
let collected = col::Collected::default();
|
||||
let endpoint = col::start_grpc_tls_collector(
|
||||
collected.clone(),
|
||||
tls.server_cert_pem.clone(),
|
||||
tls.server_key_pem.clone(),
|
||||
);
|
||||
assert!(endpoint.starts_with("https://"), "{endpoint}");
|
||||
|
||||
let mut cfg = xai_grok_telemetry::external::ExternalOtelConfig::resolve_with(
|
||||
|name| match name {
|
||||
"GROK_EXTERNAL_OTEL" => Some("1".into()),
|
||||
"OTEL_LOGS_EXPORTER" | "OTEL_METRICS_EXPORTER" => Some("otlp".into()),
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT" => Some(endpoint.clone()),
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL" => Some("grpc".into()),
|
||||
"OTEL_EXPORTER_OTLP_CERTIFICATE" => Some(ca_path.clone()),
|
||||
"OTEL_METRIC_EXPORT_INTERVAL" => Some("200".into()),
|
||||
"OTEL_BLRP_SCHEDULE_DELAY" => Some("100".into()),
|
||||
_ => None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.expect("double opt-in must resolve");
|
||||
assert_eq!(cfg.logs_ca_certificate.as_deref(), Some(ca_path.as_str()));
|
||||
cfg.client = xai_grok_telemetry::external::config::ExternalClientInfo {
|
||||
service_version: "0.0.0-test".into(),
|
||||
client_version: "0.0.0-test".into(),
|
||||
app_entrypoint: "cli".into(),
|
||||
};
|
||||
|
||||
xai_grok_telemetry::external::init(Some(cfg));
|
||||
assert!(
|
||||
xai_grok_telemetry::external::is_active(),
|
||||
"https gRPC exporters must build and activate the stream (GB-4580)"
|
||||
);
|
||||
|
||||
// `SessionNew` maps to the `session.count` metric; `SessionHarness` maps
|
||||
// to the `session_start` log record — emit both so each signal's TLS
|
||||
// export path is exercised.
|
||||
xai_grok_telemetry::log_event(xai_grok_telemetry::events::SessionNew {
|
||||
session_id: "sess-grpc-tls-1".into(),
|
||||
client_identifier: None,
|
||||
client_version: None,
|
||||
is_git_repo: true,
|
||||
permission_mode: xai_grok_telemetry::enums::PermissionMode::Ask,
|
||||
});
|
||||
xai_grok_telemetry::log_event(xai_grok_telemetry::events::SessionHarness {
|
||||
session_id: "sess-grpc-tls-1".into(),
|
||||
client_identifier: Some("grok-pager".into()),
|
||||
model_id: "grok-4".into(),
|
||||
agent_name: "grok-build-plan".into(),
|
||||
permission_mode: xai_grok_telemetry::enums::PermissionMode::Ask,
|
||||
mcp_server_names: vec![],
|
||||
plugin_names: vec![],
|
||||
skill_names: vec![],
|
||||
lsp_server_names: vec![],
|
||||
hook_names: vec![],
|
||||
agents_md_dir_names: vec![],
|
||||
memory_enabled: false,
|
||||
is_git_repo: true,
|
||||
auto_update: None,
|
||||
});
|
||||
|
||||
xai_grok_telemetry::external::flush();
|
||||
assert!(
|
||||
col::wait_until(std::time::Duration::from_secs(10), || {
|
||||
collected.logs_len() > 0
|
||||
}),
|
||||
"log records must arrive over TLS"
|
||||
);
|
||||
let names = col::event_names(&collected);
|
||||
assert!(
|
||||
names.iter().any(|n| n == "grok_code.session_start"),
|
||||
"expected grok_code.session_start in {names:?}"
|
||||
);
|
||||
|
||||
// Metrics ride the same TLS channel config; make sure at least one
|
||||
// periodic export lands too.
|
||||
assert!(
|
||||
col::wait_until(std::time::Duration::from_secs(10), || {
|
||||
collected.metrics_len() > 0
|
||||
}),
|
||||
"metric exports must arrive over TLS"
|
||||
);
|
||||
|
||||
xai_grok_telemetry::external::shutdown();
|
||||
}
|
||||
|
|
@ -174,6 +174,87 @@ async fn start_grpc_collector(collected: Collected, addr_tx: std::sync::mpsc::Se
|
|||
.expect("collector serve");
|
||||
}
|
||||
|
||||
/// A freshly generated CA plus a `localhost` server certificate signed by it,
|
||||
/// all PEM-encoded — for the TLS collector variants.
|
||||
pub struct TestTlsMaterial {
|
||||
pub ca_cert_pem: String,
|
||||
pub server_cert_pem: String,
|
||||
pub server_key_pem: String,
|
||||
}
|
||||
|
||||
/// Generate a self-signed CA and a `localhost`/`127.0.0.1` server certificate
|
||||
/// signed by it.
|
||||
pub fn generate_tls_material() -> TestTlsMaterial {
|
||||
use rcgen::{BasicConstraints, CertificateParams, IsCa, KeyPair};
|
||||
|
||||
let ca_key = KeyPair::generate().expect("generate CA key");
|
||||
let mut ca_params = CertificateParams::new(Vec::new()).expect("CA params");
|
||||
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
|
||||
let ca_cert = ca_params.self_signed(&ca_key).expect("self-sign CA");
|
||||
|
||||
let server_key = KeyPair::generate().expect("generate server key");
|
||||
let server_params =
|
||||
CertificateParams::new(vec!["localhost".to_string(), "127.0.0.1".to_string()])
|
||||
.expect("server params");
|
||||
let server_cert = server_params
|
||||
.signed_by(&server_key, &ca_cert, &ca_key)
|
||||
.expect("sign server cert");
|
||||
|
||||
TestTlsMaterial {
|
||||
ca_cert_pem: ca_cert.pem(),
|
||||
server_cert_pem: server_cert.pem(),
|
||||
server_key_pem: server_key.serialize_pem(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a **TLS** gRPC collector presenting `server_cert_pem`; returns its
|
||||
/// base URL (`https://localhost:PORT`).
|
||||
pub fn start_grpc_tls_collector(
|
||||
collected: Collected,
|
||||
server_cert_pem: String,
|
||||
server_key_pem: String,
|
||||
) -> String {
|
||||
use opentelemetry_proto::tonic::collector::logs::v1::logs_service_server::LogsServiceServer;
|
||||
use opentelemetry_proto::tonic::collector::metrics::v1::metrics_service_server::MetricsServiceServer;
|
||||
|
||||
// The test binary links both ring and aws-lc-rs, so rustls cannot pick a
|
||||
// process default on its own; the server-side acceptor needs one pinned.
|
||||
// (The production client is unaffected: tonic passes a provider
|
||||
// explicitly.)
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
|
||||
let (addr_tx, addr_rx) = std::sync::mpsc::channel::<SocketAddr>();
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("collector runtime");
|
||||
rt.block_on(async move {
|
||||
let incoming = tonic::transport::server::TcpIncoming::bind(
|
||||
"127.0.0.1:0".parse().expect("collector bind addr"),
|
||||
)
|
||||
.expect("bind gRPC TLS collector");
|
||||
addr_tx
|
||||
.send(incoming.local_addr().expect("collector addr"))
|
||||
.expect("send addr");
|
||||
let identity = tonic::transport::Identity::from_pem(server_cert_pem, server_key_pem);
|
||||
let service = GrpcCollector { collected };
|
||||
tonic::transport::Server::builder()
|
||||
.tls_config(tonic::transport::ServerTlsConfig::new().identity(identity))
|
||||
.expect("collector TLS config")
|
||||
.add_service(LogsServiceServer::new(service.clone()))
|
||||
.add_service(MetricsServiceServer::new(service))
|
||||
.serve_with_incoming(incoming)
|
||||
.await
|
||||
.expect("collector serve");
|
||||
});
|
||||
});
|
||||
let addr = addr_rx
|
||||
.recv_timeout(std::time::Duration::from_secs(10))
|
||||
.expect("collector must start");
|
||||
format!("https://localhost:{}", addr.port())
|
||||
}
|
||||
|
||||
pub fn decode_logs(
|
||||
collected: &Collected,
|
||||
) -> Vec<opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue