Enable git attribution across Codex entry points (#34819)

## What changed

- Install the git attribution extension in the app server, MCP server, and `codex debug prompt-input` so authenticated workspace policy controls the commit and pull request attribution instructions sent to the model.
- Resolve attribution settings from the process-level ChatGPT base URL, independent of per-thread or per-tool configuration overrides.
- Treat git attribution as contextual developer content when mapping model events.

## Testing

- Cover policy fetch retries, workspace switches, rollbacks, cold thread resumes with legacy instructions, MCP tool calls, and prompt-debug output.

GitOrigin-RevId: 57d182c432c20d7c1c6c429057b6163c9f32088d
This commit is contained in:
jay
2026-07-22 20:23:35 +00:00
committed by copyberry
parent 4ebd976312
commit 88eb3a2b8a
21 changed files with 600 additions and 166 deletions
@@ -3,12 +3,6 @@ use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::time::Duration;
use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionRegistry;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::PreviousWorldStateSection;
use codex_extension_api::WorldStateContributionInput;
use codex_extension_api::WorldStateSectionContribution;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_login::AuthManager;
@@ -16,9 +10,6 @@ use codex_login::CodexAuth;
use codex_login::ExternalAuth;
use codex_login::ExternalAuthFuture;
use codex_login::ExternalAuthRefreshContext;
use codex_protocol::ThreadId;
use pretty_assertions::assert_eq;
use serde_json::Value;
use tokio::sync::Notify;
use wiremock::Mock;
use wiremock::MockServer;
@@ -26,32 +17,7 @@ use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
use super::install;
use super::policy::resolve_attribution_policy;
use super::world_state::DISABLED_INSTRUCTIONS;
use super::world_state::ENABLED_INSTRUCTIONS;
use super::world_state::LEGACY_COMMIT_ATTRIBUTION_INSTRUCTIONS;
async fn contribute(
registry: &ExtensionRegistry<String>,
thread_store: &ExtensionData,
) -> WorldStateSectionContribution {
let session_store = ExtensionData::new("session");
let turn_store = ExtensionData::new("turn");
registry.context_contributors()[0]
.contribute_world_state(WorldStateContributionInput {
thread_id: ThreadId::new(),
turn_id: "turn",
environments: &[],
ready_selected_capability_roots: &[],
executor_capability_discovery: None,
session_store: &session_store,
thread_store,
turn_store: &turn_store,
})
.await
.remove(0)
}
fn enterprise_auth_manager() -> Arc<AuthManager> {
AuthManager::from_auth_for_testing(enterprise_auth("workspace-123"))
@@ -66,15 +32,6 @@ fn enterprise_auth(account_id: &str) -> CodexAuth {
.expect("fake ChatGPT auth should parse")
}
async fn mount_settings(server: &MockServer, response: ResponseTemplate) {
Mock::given(method("GET"))
.and(path("/backend-api/wham/settings/user"))
.respond_with(response)
.expect(1)
.mount(server)
.await;
}
struct StaticExternalAuth(CodexAuth);
impl ExternalAuth for StaticExternalAuth {
@@ -94,116 +51,6 @@ async fn set_auth(auth_manager: &AuthManager, account_id: &str) {
.expect("auth refresh should succeed");
}
#[tokio::test]
async fn installed_contributor_composes_policy_changes() {
let server = MockServer::start().await;
let base_url = format!("{}/backend-api", server.uri());
let api_key = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test"));
assert!(
!resolve_attribution_policy(&api_key, &base_url, &http_client_factory())
.await
.expect("API key policy should not time out")
.expect("API key policy should resolve")
.enabled
);
let auth_manager = enterprise_auth_manager();
let mut builder = ExtensionRegistryBuilder::<String>::new();
install(
&mut builder,
auth_manager.clone(),
base_url.clone(),
http_client_factory(),
);
let registry = builder.build();
let thread_store = ExtensionData::new("thread");
mount_settings(
&server,
ResponseTemplate::new(200)
.set_delay(Duration::from_secs(1))
.set_body_json(serde_json::json!({"commit_attribution_enabled": true})),
)
.await;
let unavailable = contribute(&registry, &thread_store).await;
assert_eq!(unavailable.snapshot(), &Value::Bool(false));
assert!(
unavailable
.render_diff(PreviousWorldStateSection::Absent)
.is_none()
);
assert!(!unavailable.has_retained_fragment_matcher());
assert!(
unavailable.matches_legacy_fragment("developer", LEGACY_COMMIT_ATTRIBUTION_INSTRUCTIONS)
);
assert_eq!(
unavailable
.render_diff(PreviousWorldStateSection::Unknown)
.map(|fragment| fragment.body().to_string()),
Some(DISABLED_INSTRUCTIONS.to_string())
);
assert_eq!(
contribute(&registry, &thread_store).await.snapshot(),
&Value::Bool(false)
);
server.verify().await;
server.reset().await;
mount_settings(&server, ResponseTemplate::new(503)).await;
set_auth(auth_manager.as_ref(), "workspace-456").await;
let failed = contribute(&registry, &thread_store).await;
assert_eq!(failed.snapshot(), &Value::Bool(false));
assert!(
failed
.render_diff(PreviousWorldStateSection::Absent)
.is_none()
);
server.verify().await;
server.reset().await;
mount_settings(
&server,
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({"commit_attribution_enabled": true})),
)
.await;
set_auth(auth_manager.as_ref(), "workspace-789").await;
let enabled = contribute(&registry, &thread_store).await;
let enabled_fragment = enabled
.render_diff(PreviousWorldStateSection::Known(&Value::Bool(false)))
.expect("enabled policy should replace disabled policy");
assert_eq!(enabled_fragment.body(), ENABLED_INSTRUCTIONS);
let (start, end) = enabled_fragment.markers();
let rendered = format!("{start}{}{end}", enabled_fragment.body());
assert!(enabled.matches_legacy_fragment(enabled_fragment.role(), &rendered));
assert!(enabled.matches_retained_fragment(enabled_fragment.role(), &rendered));
assert!(!enabled.matches_legacy_fragment("developer", LEGACY_COMMIT_ATTRIBUTION_INSTRUCTIONS));
assert!(
contribute(&registry, &thread_store)
.await
.render_diff(PreviousWorldStateSection::Known(&Value::Bool(true)))
.is_none()
);
server.verify().await;
server.reset().await;
mount_settings(
&server,
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({"commit_attribution_enabled": false})),
)
.await;
set_auth(auth_manager.as_ref(), "workspace-disabled").await;
let disabled = contribute(&registry, &thread_store).await;
assert_eq!(
disabled
.render_diff(PreviousWorldStateSection::Known(&Value::Bool(true)))
.map(|fragment| fragment.body().to_string()),
Some(DISABLED_INSTRUCTIONS.to_string())
);
assert!(
disabled
.render_diff(PreviousWorldStateSection::Absent)
.is_none()
);
server.verify().await;
}
#[tokio::test]
async fn policy_resolution_recovers_after_unauthorized() {
let server = MockServer::start().await;