Repository navigation
[AIGTWY-4901] Record the settings key paths ug writes - #920
david-siqi-liu wants to merge 1 commit into
Conversation
5f7fc91 to
35ebe60
Compare
07e74c7 to
609ed43
Compare
af1b80f to
c222dab
Compare
UG reviewAdds a persistent per-file ownership store and reconciliation helpers, with revert now deleting the store. Configure and launch do not yet record or consume provenance. Minor
Automated advisory review of |
|
Fixed in 0f4fa29: |
c222dab to
0f4fa29
Compare
|
Major: fixed in d22ee06. Minor (private |
d22ee06 to
7d82776
Compare
|
Major (absent-store cleanup): fixed in 7d82776. Minor (private helpers): same as above, deferring the public API for the |
fda8d8b to
2f84de4
Compare
2f84de4 to
4c75920
Compare
Cleanup in the Claude and Codex writers removes settings by key name, so it also deletes values another tool or the developer wrote. This adds the provenance record later changes use to prove authorship: for each agent and destination file, the key paths ug wrote and the exact values it wrote there. - provenance.py stores the record under the managed-backups directory, guarded by a file lock so concurrent ug runs don't drop each other's records. - owned_after_write keeps only values that landed, or that ug wrote earlier and nobody changed; retire and retire_group restore a baseline value or delete only owned, unedited paths. Comparisons are type-strict. - values_at reads the values a document holds at given key paths. - ug revert clears the store after clearing state. Co-authored-by: Isaac <no-reply@databricks.com>
4c75920 to
fff7dff
Compare
What this adds and why
ug writes into config files it shares with other writers: Claude Code's
~/.claude/ucode-settings.jsonand the OS-managedmanaged-settings.json, and Codex's config files. Those files also hold settings an admin, Isaac, MDM, or the developer put there. When a managed config changes (tracing turned off, a model list removed), ug has to clean up what it previously wrote, and today it removes keys by name, which can delete a value someone else owns at the same key.This PR adds the record that lets later cleanup be surgical. For each agent and destination file, it stores the key paths ug wrote and the exact value it wrote at each. A key is removed or restored only when the file still holds the value ug last wrote there; if anyone else changed it, ug leaves it alone. No writer records or consumes it yet; the following PRs make
ug configuredo both.ug revertclears it.The store lives at
~/.ucode/managed-backups/provenance.json, keyed by agent and destination path so it survives workspace switches, and is written under a file lock so concurrent ug runs don't drop each other's records. Comparisons are type-strict, and an explicit null is distinct from an absent key.What goes inside
For each agent and file, a list of
{path, value}entries: the setting ug wrote and the value it wrote.It is deliberately not a 1:1 list of every field ug manages. It holds only the keys a cleanup path actually consults. For Claude that is effectively its full written set. For Codex today it is just
model, the one field ug conditionally removes; the Codex provider block and HTTP headers are reconciled a different way and are not recorded. Values other writers put in the files are never recorded, that is the whole point.Full store after a managed
ug configurefor both agents (Claude static model list, tracing on){ "files": { "claude:/root/.claude/ucode-settings.json": [ { "path": [ "apiKeyHelper" ], "value": "/usr/local/bin/ug auth-token --host https://eng-ml-inference.staging.cloud.databricks.com" }, { "path": [ "availableModels" ], "value": [ "system.ai.claude-haiku-4-5" ] }, { "path": [ "enforceAvailableModels" ], "value": true }, { "path": [ "env", "ANTHROPIC_BASE_URL" ], "value": "https://eng-ml-inference.staging.cloud.databricks.com/ai-gateway/anthropic" }, { "path": [ "env", "ANTHROPIC_CUSTOM_HEADERS" ], "value": "x-databricks-use-coding-agent-mode: true\nUser-Agent: ucode/0.0.0 claude/2.1.280" }, { "path": [ "env", "ANTHROPIC_DEFAULT_HAIKU_MODEL" ], "value": "system.ai.claude-haiku-4-5" }, { "path": [ "env", "ANTHROPIC_DEFAULT_OPUS_MODEL" ], "value": "system.ai.claude-opus-4-5" }, { "path": [ "env", "ANTHROPIC_DEFAULT_SONNET_MODEL" ], "value": "system.ai.claude-sonnet-4-5" }, { "path": [ "env", "CLAUDE_CODE_API_KEY_HELPER_TTL_MS" ], "value": "900000" }, { "path": [ "env", "CLAUDE_CODE_ENABLE_TELEMETRY" ], "value": "1" }, { "path": [ "env", "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA" ], "value": "1" }, { "path": [ "env", "CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS" ], "value": "900000" }, { "path": [ "env", "CLAUDE_CODE_PROPAGATE_TRACEPARENT" ], "value": "1" }, { "path": [ "env", "CLAUDE_CODE_USE_GATEWAY" ], "value": "1" }, { "path": [ "env", "ENABLE_PROMPT_CACHING_1H" ], "value": "1" }, { "path": [ "env", "ENABLE_TOOL_SEARCH" ], "value": "true" }, { "path": [ "env", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" ], "value": "https://eng-ml-inference.staging.cloud.databricks.com/ai-gateway/otel/v1/traces" }, { "path": [ "env", "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" ], "value": "http/protobuf" }, { "path": [ "env", "OTEL_TRACES_EXPORTER" ], "value": "otlp" }, { "path": [ "modelPicker" ], "value": { "replaceBuiltInOptions": true, "options": [ { "model": "system.ai.claude-haiku-4-5", "label": "Claude Haiku 4.5" } ] } }, { "path": [ "otelHeadersHelper" ], "value": "/usr/local/bin/ug otel-headers --host https://eng-ml-inference.staging.cloud.databricks.com" }, { "path": [ "permissions", "deny" ], "value": [ "WebSearch" ] } ], "claude:/etc/claude-code/managed-settings.json": [ { "path": [ "apiKeyHelper" ], "value": "/usr/local/bin/ug auth-token --host https://eng-ml-inference.staging.cloud.databricks.com" }, { "path": [ "availableModels" ], "value": [ "system.ai.claude-haiku-4-5" ] }, { "path": [ "enforceAvailableModels" ], "value": true }, { "path": [ "env", "ANTHROPIC_BASE_URL" ], "value": "https://eng-ml-inference.staging.cloud.databricks.com/ai-gateway/anthropic" }, { "path": [ "env", "ANTHROPIC_CUSTOM_HEADERS" ], "value": "x-databricks-use-coding-agent-mode: true\nUser-Agent: ucode/0.0.0 claude/2.1.280" }, { "path": [ "env", "ANTHROPIC_DEFAULT_HAIKU_MODEL" ], "value": "system.ai.claude-haiku-4-5" }, { "path": [ "env", "CLAUDE_CODE_API_KEY_HELPER_TTL_MS" ], "value": "900000" }, { "path": [ "env", "CLAUDE_CODE_ENABLE_TELEMETRY" ], "value": "1" }, { "path": [ "env", "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA" ], "value": "1" }, { "path": [ "env", "CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS" ], "value": "900000" }, { "path": [ "env", "CLAUDE_CODE_PROPAGATE_TRACEPARENT" ], "value": "1" }, { "path": [ "env", "CLAUDE_CODE_USE_GATEWAY" ], "value": "1" }, { "path": [ "env", "ENABLE_PROMPT_CACHING_1H" ], "value": "1" }, { "path": [ "env", "ENABLE_TOOL_SEARCH" ], "value": "true" }, { "path": [ "env", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" ], "value": "https://eng-ml-inference.staging.cloud.databricks.com/ai-gateway/otel/v1/traces" }, { "path": [ "env", "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" ], "value": "http/protobuf" }, { "path": [ "env", "OTEL_TRACES_EXPORTER" ], "value": "otlp" }, { "path": [ "modelPicker" ], "value": { "replaceBuiltInOptions": true, "options": [ { "model": "system.ai.claude-haiku-4-5", "label": "Claude Haiku 4.5" } ] } }, { "path": [ "otelHeadersHelper" ], "value": "/usr/local/bin/ug otel-headers --host https://eng-ml-inference.staging.cloud.databricks.com" }, { "path": [ "permissions", "deny" ], "value": [ "WebSearch" ] } ], "codex:/root/.codex/ucode.config.toml": [ { "path": [ "model" ], "value": "system.ai.gpt-5-4-nano" } ], "codex:/etc/codex/managed_config.toml": [ { "path": [ "model" ], "value": "system.ai.gpt-5-4-nano" } ] } }Adds an
AGENTS.mdsection ("Howugtracks what it wrote (provenance)") explaining the record's purpose and mechanism.Part of AIGTWY-4901. Intended behavior follows the field allow list in
AGENTS.md.This pull request and its description were written by Isaac.