Skip to content

chore(config): migrate logging translation to typed config#2000

Closed
webern wants to merge 30 commits into
m/pr6-adp-cutoverfrom
m/logging-conf
Closed

chore(config): migrate logging translation to typed config#2000
webern wants to merge 30 commits into
m/pr6-adp-cutoverfrom
m/logging-conf

Conversation

@webern

@webern webern commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Human Summary

There's crazy stuff happening in this one. I am not going to merge it into the aggregating PR that is part of the merge stack. This will me left as a follow up into main if we end up merging the stack.

AI Summary

Cut LoggingConfigurationTranslator and DynamicLogLevelWorker over from the raw
GenericConfiguration map to the typed SalukiConfiguration model.

All eleven logging keys the translator reads (log_level, log_format_json,
log_format_rfc3339, log_to_console, log_to_syslog, syslog_rfc, syslog_uri,
log_file_max_size, log_file_max_rolls, disable_file_logging, data_plane.log_file) are
witnessed against the vendored Datadog schema and were already driven into control.logging.
The translator now builds LoggingConfiguration from that typed model instead of re-parsing raw
config, keeping only the ADP-specific business rules: first-party log-filter expansion, syslog
gating with a platform-default URI fallback, and the per-subagent log-file path with a
platform-default fallback (the Core Agent's own log_file is still never consulted).

DynamicLogLevelWorker no longer stores a GenericConfiguration; it reacts to a
Live<String> view of control.logging.level and re-applies the parsed filter on change.

Logging is initialized before the configuration system exists, so this adds a lenient one-shot
translate_snapshot to the config-system crate (translation errors keep field defaults, mirroring
the runtime update path) and uses it at the two bootstrap-phase sites: the initial logging setup in
main and the config-stream reload in run. The control-plane wiring now takes
&ConfigurationSystem and mints both the log-level view and the /config/internal handle from it.

Change Type

  • Non-functional (chore, refactoring, docs)

How did you test this PR?

  • Rewrote the logging tests to build the typed model directly and cover filter expansion, syslog
    gating, log-file fallback/disable, and byte-size conversion; added a driven-Live test that the
    worker reacts to a live log-level change.
  • Added translator tests covering the schema-driven logging defaults, driven values (including the
    log_file_max_size byte-size parse), and the invalid-byte-size error path.
  • make build-schema-overlay && make fmt (no drift), make check-all, make test, make check-docs.

References

webern added 24 commits July 3, 2026 19:35
Foundation for strongly-typed ADP configuration. No binary behavior change; it
sets up the base for later PRs that migrate from GenericConfiguration map access
to typed SalukiConfiguration access.

- adds the translation target: SalukiConfiguration
- adds a compile-time auditable translation system: DatadogConfigWitness
- translates DatadogConfiguration into SalukiConfiguration
- discovers and promotes config keys that were missing from the inventory
Environment variables reach the config map as flat, underscore-joined
top-level keys (`DD_AUTOSCALING_FAILOVER_ENABLED` -> `autoscaling_failover_enabled`),
while `DatadogConfiguration` deserializes the nested shape. The whole-struct
deserialize never reads those flat keys, so an env var set for a multi-segment
key is silently dropped. The legacy per-key lookup bridged this at query time by
retrying the dotted key with `.` replaced by `_`; the typed path issues no
per-key queries, so that bridge no longer fires.

Reinstate it as a one-time pass over the merged value, driven by a generated
table of the supported multi-segment keys. For each known dotted path, relocate
any matching flat key into the nested slot the deserializer reads. This runs the
safe direction (known path -> its single flat form), so it never has to guess
where an arbitrary flat key's nesting boundaries are. String lists are split on
whitespace to match the Agent's env convention; scalars relocate verbatim.

The mode is chosen at deserialization: Fallback fills only slots the Agent left
empty (matching the legacy precedence, restoring prior behavior), Override lets
an env var replace an Agent-supplied value, and Disabled relocates nothing. The
overlay is applied only to the Datadog input; the Saluki-only source
deserializes from the untouched value and is out of scope here.

(cherry picked from commit 7e6e4095dbfc12224773216c4c6ecf6611103442)
Preparatory fixes the component cutovers depend on; none of these change a
component yet.

- Load ConfigurationSystem before topology assembly and thread the system
  handle down to the DogStatsD pipeline builder (unused for now, hence the
  leading underscore).
- Reshape the Saluki-only source struct to mirror the config key hierarchy
  exactly, so each field maps by plain serde to its real config-key path.
- Model the memory limit as a byte size (u64 in the model, ByteSize in the
  source) so a bare-integer value no longer fails the whole config load.
Environment variables reach the merged map as flat, underscore-joined keys, but SalukiOnly deserializes a nested shape, so a DD_* var for a dotted Saluki-only path (e.g. DD_DATA_PLANE_STANDALONE_MODE) was silently dropped. The Datadog overlay already fixes this for schema keys via a generated table; SalukiOnly has no table by design.

Discover SalukiOnly's canonical leaf paths algorithmically from its derived Deserialize using a hand-rolled recording deserializer (no new dependency), treating scalar-like custom types (DurationString, ByteSize, Duration) as leaves. Cache the paths. Before deserializing SalukiOnly, relocate each multi-segment path's flat form into its nested slot, honoring EnvOverlayMode (Disabled/Fallback/Override). Single-segment keys are already flat and untouched.
Cut the aggregate transform over from raw GenericConfiguration access to the
typed SalukiConfiguration model. AggregateConfiguration builds from the
dogstatsd aggregation slice and the shared histogram encoding; histogram
parsing moves into HistogramConfiguration::from_encoding, and the component
config struct no longer carries a Default or deserializes Datadog config.

The aggregation slice is made authoritative from the Agent schema, dropping
the redundant Saluki-only aggregation keys. run.rs reads the typed config and
hands the transform its subtree.
…onfig

Cut the debug-log destination over to the typed SalukiConfiguration model.
DogStatsDDebugLogConfiguration builds from the dogstatsd debug-log slice and
holds a Live view, so the destination reacts to metrics-stats changes through
the live view instead of a string-key watch. run.rs hands it the typed subtree
and mints the live view from the configuration system.
## AI Summary

Build the OTTL filter processor from the resolved traces-domain
configuration instead of parsing `GenericConfiguration` in the
component. Remove the component-owned source serde configuration and use
the shared `OttlErrorMode` model type.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?
- `make build-schema-overlay && make fmt`
- `make check-all`
- `make test`
- `make check-docs`
- Targeted OTTL filter processor tests

## References

Related to configuration cutover PR #1979.
)

## AI Summary

Build `DatadogEventsConfiguration` from the typed shared configuration
model instead of deserializing the generic configuration map. Remove
component-local source defaults and retire the configuration smoke test.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt`
- `make check-fmt check-docs`
- `make test`
- `make check-deny check-unused-deps check-licenses check-features
generate-api-docs`
- `cargo check -p saluki-components -p agent-data-plane`

## References

No issue.
Build the Datadog Logs encoder from the shared typed compression
configuration and remove its raw configuration deserialization.

- [x] Non-functional (chore, refactoring, docs)

- `make build-schema-overlay && make fmt`
- `make check-all`
- `make test`
- `make check-docs`
## AI Summary

Build autoscaling failover from the translated shared configuration
instead of reading
`GenericConfiguration` directly. The topology builder now passes the
typed model slice into the
component, and component tests construct the typed configuration
directly.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt`
- `cargo check -p agent-data-plane`
- `cargo test -p saluki-components --lib` (744 passed, 1 ignored)
- `make check-docs`
- `make check-all` passed formatting, clippy, docs, dependency checks,
API docs, and began the
  feature matrix before timing out during the remaining matrix checks.

## References

- Progresses #1788
## AI Summary

Build multi-region failover configuration from the typed
`SalukiConfiguration` domain model and preserve endpoint normalization
during translation.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt`
- `make check-all`
- `make test`
- `make check-docs`
- Targeted MRF and configuration-system unit tests

## References

- Progresses #1788
## AI Summary

Build `TraceSamplerConfiguration` from the resolved traces configuration
instead of the raw configuration map. Move trace sampler defaults into
the typed configuration layers and remove the sampler-specific source
parsing from the shared APM helper.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt`
- `make check-all`
- `make test`
- `make check-docs`

## References

- Progresses #1788
## AI Summary

Migrates `ClusterAgentConfiguration` off the raw `GenericConfiguration`
map onto
the typed configuration model. The struct is now built from the
translated
`shared::ClusterAgent` slice; the `cluster_agent.*` keys are witnessed
(present
in the vendored Datadog schema) and already resolved into the model.

Trimming of the Cluster Agent URL, auth token, and Kubernetes service
name moves
up into the config layer (the witness translator), since trailing
whitespace
would otherwise corrupt the value at the point of use (URL parsing,
bearer
token, service-name-derived env lookup). Component construction just
copies the
already-normalized fields, and its `from_configuration` signature
changes from
`&GenericConfiguration` to `&shared::ClusterAgent`.

The call site in `run.rs` now passes the typed `shared.cluster_agent`
slice
alongside the existing `shared.autoscaling_failover` slice.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- Component construction tests rewritten to build from `ClusterAgent`
model
slices directly (enabled gating, URL scheme normalization, Kubernetes
service
  resolution, IPv6 host wrapping, URL-vs-service precedence).
- Added translator tests covering the relocated normalization:
surrounding
whitespace is trimmed and empty/whitespace-only values collapse to
`None`, and
an absent `kubernetes_service_name` picks up the Datadog schema default.
- `make build-schema-overlay && make fmt` (no file changes), `make
check-all`,
  `make test`, `make check-docs` all pass.

### Follow-up Fixed by AI Review

Claude Code operating on behalf of webern.

Follow-up review of `216f6836f89f333773e21fc04032134a2b2729a3`: the
reported regression is fixed. The translator now preserves the
distinction between the schema default and an explicitly blank
`cluster_agent.kubernetes_service_name`: absent input still becomes
`Some("datadog-cluster-agent")`, configured values are trimmed, and
empty or whitespace-only input becomes `Some("")`. That empty value
reaches `resolve_endpoint`, which suppresses Kubernetes service
discovery even when the default service environment variables are
present. URL and auth-token normalization remain unchanged.

I verified coverage for the schema default, explicit empty input,
whitespace-only input, and the component-level suppression behavior with
default service environment variables available. The fix is narrowly
scoped, and the relevant formatting, Clippy, schema-overlay, and
unit-test CI checks reported success during verification.

The original review concern is resolved. This is good to go from the
config-cutover review perspective.

## References

- Progresses #1788
- Merges into `m/pr5-cutover`
## Human Summary

There's some weird stuff going on here with preserving "absence" in an
attempt to preserve exact behavioral parity, but I think it's OK.

## AI Summary

Cuts the OTLP components over from the raw `GenericConfiguration` map to
the typed
`SalukiConfiguration` model, following the PR #1979 pattern:
`OtlpConfig` (shared receiver
config), the OTLP source, decoder, forwarder, and relay.

Each component now builds from typed model slices (`domains.otlp`,
`domains.traces.otlp`)
instead of deserializing the raw config:

- **Source / relay / decoder / forwarder**: `from_configuration` takes
borrowed model slices;
the call site in `run.rs` reads them from the config system. Component
structs carry no serde
  and no defaults.
- **Seeded OTLP knobs** (context sizing, HTTP receiver transport, trace
interner size,
top-level-by-span-kind) are converted to the required-with-default
doctrine: non-optional in
`SalukiOnly` with the default defined once as a shared `const`/`fn` in
`agent-data-plane-config`
and referenced by both the model `Default` and the `SalukiOnly` serde
default. The two byte-size
interner keys are typed `ByteSize` so they accept both a bare integer
and a suffixed string.
- **`OtlpTracesTranslator`** now takes the two native booleans it
actually reads instead of the
whole raw `TracesConfig`. `TracesConfig` is kept only for the
not-yet-migrated Datadog trace
  encoder.
- The per-component config smoke tests are retired (coverage now lives
in the translator and
  construction tests).

### Behavior notes

Because the OTLP receiver keys are witnessed against the vendored
Datadog schema, their defaults
now come from that schema rather than Saluki's former hardcoded values.
When a key is left unset:

- gRPC/HTTP receiver endpoints default to `localhost:4317` /
`localhost:4318` (previously
  `0.0.0.0:...`), matching the Datadog Agent.
- `otlp_config.logs.enabled` defaults to `false` (previously `true`),
matching the Datadog Agent.
- `otlp_config.receiver.protocols.grpc.max_recv_msg_size_mib` of `0` now
maps to grpc-go's built-in
4 MiB limit, matching the Agent and the schema documentation (previously
`0` produced a 0-byte
  limit).

Deployments that set these keys explicitly (including the OTLP
correctness cases, which pin the
gRPC endpoint to `0.0.0.0:4317`) are unaffected.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt` (no generated drift), `make
check-all`, `make check-docs`.
- Unit tests for `agent-data-plane-config`,
`agent-data-plane-config-system`, `saluki-components`,
and `agent-data-plane`, including new `SalukiOnly` transport/default
tests (both byte-size input
  forms) and per-component construction tests.
- Statically reviewed the OTLP correctness cases: all send over gRPC on
the explicitly configured
`0.0.0.0:4317`, so the receiver default changes above do not affect
them.

## References

- Progresses #1788
- Merges into `m/pr5-cutover`
## Human Summary

TODO: human writes here

## AI Summary

Build the APM/trace pipeline components from the typed
`SalukiConfiguration`
model instead of the raw configuration map, and remove the shared
`ApmConfig`
parsing helper they all leaned on.

Migrated components:
- `DatadogApmStatsEncoderConfiguration` (APM stats encoder)
- `DatadogTraceConfiguration` (Datadog trace encoder)
- `TraceObfuscationConfiguration` (trace obfuscation transform)
- `ApmStatsTransformConfiguration` (APM stats transform)

Details:
- Trace settings (env, sampling targets, error tracking, peer tags,
obfuscation, OTLP trace knobs) are read from `domains.traces`. The
shared
  encoder flush timeout is read from `shared.metrics_encoding`, and the
  compression settings from `shared.endpoints.compression`.
- The obfuscator's `ObfuscationConfig` is built from the typed
`Obfuscation`
  model via a `From` conversion, so the obfuscation engine is untouched.
- `ApmConfig` in `common/datadog/apm.rs` is deleted; nothing else
consumed it.
- The encoder flush timeout (`flush_timeout_secs`) becomes a required
Saluki-only value whose 2s default is hoisted into the config layer and
  shared across the metrics, trace, and APM stats encoders.
- The per-struct config smoke tests are retired in favor of translator
and
  component construction tests; classification metadata is unchanged.

Behavior is preserved: witnessed keys keep their schema defaults (which
match
the old component defaults), and the OTLP probabilistic sampler default
and
`env` fallback normalize to the same effective values as before.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt` (no file changes)
- `make check-all`
- `make test`
- `make check-docs`

## References

- Progresses #1788
- Merges into `m/pr5-cutover`
The OTLP and APM/trace cutovers removed all consumers of common/otlp/config.rs, but squash-merging the spike PRs left the module declared. Remove it to fix the resulting dead-code errors from the PR-5 merge fallout.

Merge fallout from #1989 and #1991.
…to typed config (#1990)

## AI Summary

Migrates four more pipeline components off the raw
`GenericConfiguration` map
and onto the typed `SalukiConfiguration` model.

- **Datadog Metrics encoder** builds from `shared.metrics_encoding` and
`shared.endpoints`. The model's `V3ApiEncoding` / `V3SeriesMode` types
convert
into the encoder's runtime `V3ApiConfig` / `UseV3ApiSeriesConfig` via
`From`
impls in `protocol.rs`; the additional-endpoints presence check reads
the model
map directly (the now-unused `AdditionalEndpoints::is_empty` is
removed).
- **Datadog Service Checks encoder** builds from
`shared.metrics_encoding` and
  `shared.endpoints.compression`, mirroring the Events encoder.
- **Checks IPC source** reads `domains.checks.ipc_endpoint` and parses
it into a
  `ListenAddress` at the boundary.
- **MRF metrics gateway** becomes a dynamic component: it takes
`Live<multi_region_failover::Domain>` and re-derives its routing mode
from the
  live domain instead of watching raw keys.

Defaults move up to the config layer, following the aggregate-slice
pattern
(real default in the model `Default`, Saluki-only source stays `Option`
with a
conditional seed):

- `MetricsEncoding::default` now sets the encoder flush timeout (2s) and
  max-metrics-per-payload (10,000).
- `checks::Domain::default` now sets the IPC endpoint
(`tcp://0.0.0.0:5105`).

The component structs no longer carry source serde or their own
defaults, and the
per-struct config smoke tests are retired; coverage now lives in the
translator
tests and the components' construction tests.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt` (no changes)
- `make check-all`
- `make check-docs`
- `cargo nextest run --lib --bins` for the affected crates
- New construction/dynamic-update tests for each migrated component

### Final AI Review Output

Claude Code operating on behalf of webern — fresh clean-room pass on the
current HEAD (fixup included).

The V3 series endpoint-mode sanitization regression from the earlier
pass is now fixed and well-covered: strings pass through, bools
normalize to `"true"`/`"false"`, and every other JSON kind records a
translation error (rejecting at startup) while valid entries still
survive a runtime update. The rest of the cutover looks transparent —
checks-IPC default `tcp://0.0.0.0:5105` matches the old `any_tcp(5105)`
and still fails startup on a bad address; the metrics/service-checks
payload, compression, and flush defaults are preserved (including the `0
→ 10ms` flush floor and the zstd agent-default→3 swap); the
`has_additional_endpoints` V3 gate matches the old `!is_empty()`; and
the MRF gateway's `Live<Domain>` cutover is equivalent to the old
two-key watchers (build-time mode and the live view both project the
same `multi_region_failover` domain, and `Live::changed()` only wakes on
a real change to that projection).

One non-blocking note on test coverage. This PR retires two per-struct
tests that guarded **absent-key** defaults:

- `use_v2_api_series_default::defaults_to_true_when_absent` (guaranteed
`use_v2_api_series` defaults to `true`)
- the `max_series_points_per_payload` default assertion (guaranteed
`10_000` when absent)

The replacements only assert that explicit values map through the model
— nothing now asserts the effective default when the key is absent. Both
are witnessed keys, so the real default arrives from the schema via the
witness driver, and the model `Default` sets `use_v2_series_api: false`
as a placeholder. That's correct today (schema default is `true` and
`drive()` runs unconditionally), but if the witness wiring ever drifts
the default silently flips V2→V1 series routing with no test catching
it. The seeded defaults added here (`max_metrics_per_payload`, checks
`ipc_endpoint`) are guarded by `absent_keys_leave_model_defaults`, but
that test only runs `seed()`, so it can't cover the witnessed ones.

Suggest adding a `translate()`-level assertion on an empty config that
`use_v2_series_api == true` (and ideally `max_series_points_per_payload
== 10_000`) to restore the lost guard. Non-blocking — the current
behavior is correct.


## References

- Progresses #1788
- Merges into `m/pr5-cutover`
…1992)

## AI Summary

Build the DogStatsD source and metric mapper from the typed
`SalukiConfiguration` model
(`domains.dogstatsd`) instead of the raw `GenericConfiguration` map.

- **Source** (`DogStatsDConfiguration`): `from_configuration` now takes
`&domains::dogstatsd::Domain`
plus a caller-derived default capture directory. `run_path` is
deliberately unmodeled, so the call
site reads it from the raw config and passes `run_path/dsd_capture` in;
the component no longer
touches `GenericConfiguration`. All source serde (`Deserialize`,
renames, `default_*` fns) is
stripped; injected runtime state (workload provider, capture/replay
control) stays. Origin
enrichment builds from the model via
`OriginEnrichmentConfiguration::from_model`.
- **Mapper** (`DogStatsDMapperConfiguration`): `from_configuration`
takes `&domains::dogstatsd::Mapper`
and builds the `MetricMapper` from typed `MapperProfile`s. The interner
byte budget parses as
  `ByteSize` in `SalukiOnly` (so `64KiB` and a bare integer both load).
- **Defaults**: the Saluki-only DogStatsD listener/context/mapper
defaults (`buffer_count`,
`buffer_count_max`, `permissive_decoding`, `cached_contexts_limit`,
`cached_tagsets_limit`,
`allow_context_heap_allocs`, `minimum_sample_rate`, mapper
`string_interner_size`) move into
hand-written model `Default` impls, so an absent key yields the value
the components expect.
  Witnessed keys keep their schema-driven defaults.
- Retire the two component config smoke tests (the structs no longer
deserialize); construction and
parsing coverage now lives in the translator/`SalukiOnly` tests and the
components' own tests.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt` (no file changes)
- `make check-all`
- `make test`
- `make check-docs`

### Last of Several AI PR Reviews

Claude Code, operating on behalf of @webern.

Verified at `eec29828`: all three alternate-input-form regressions are
resolved, and I think this is ready.

- `dogstatsd_string_interner_size_bytes` — the `SalukiOnly` field is now
`Option<ByteSize>` with `.as_u64()` in `seed()`, mirroring the mapper
interner, so the suffix-string form (`"2MiB"`) loads again alongside the
bare integer.
- `dogstatsd_eol_required` — `normalize_datadog_input_forms` splits the
space-separated string in the input seam (after the env overlay, before
`DatadogConfiguration::deserialize`), matching
`deserialize_space_separated_or_seq`.
- `dogstatsd_mapper_profiles` — the same helper parses the JSON-string
form into the array the translator expects, leaving malformed JSON in
place so the normal error still surfaces.

Each form has both alternate- and native-shape coverage through
`ConfigurationSystem::load` / `seed`, replacing the deleted
component-level tests at the right layer. Only `saluki_only.rs` and
`system.rs` changed — no generated files or model types touched. The
defaults and field mappings I checked earlier still hold.

The deterministic static gates are green (`check-schema-overlay`,
`check-fmt`, `check-deny`, `check-docs`, `check-unused-deps`,
mergegate); unit tests are still running. No further blocking items from
me.

## References

- Progresses #1788
- Merges into `m/pr5-cutover`
## Human Summary

Caught by AI when reviewing #1993, it looks like this was a regression
in the environment variable path only, which *should not* have affected
customers anyway, but it looks like now we handle JSON in environment
variable correctly if we happen to ever read that.

## AI Summary

Restores the wider `additional_endpoints` input shapes that were lost
when the metrics encoder and
forwarder moved off the component serde onto the typed model.

The old `AdditionalEndpoints` serde used `PickFirst<(DisplayFromStr,
_)>` plus `OneOrMany`, so it
accepted:

- the whole map as a JSON string — the form an environment variable
produces, e.g.
  `DD_ADDITIONAL_ENDPOINTS='{"https://app.datadoghq.com":["key"]}'`, and
- a bare string in place of a one-element key list for a host.

The generated `DatadogConfiguration` deserializes `additional_endpoints`
as a plain
`HashMap<String, Vec<String>>`, and the env overlay only materializes
scalar/space-separated-list
env values, so neither shape survives. Both now fail deserialization
outright — a dual-shipping
deployment configured through the environment fails to start.

This adds an `additional_endpoints` case to
`normalize_datadog_input_forms` (the same pass that
already restores the `dogstatsd_eol_required` and
`dogstatsd_mapper_profiles` env-var forms): parse
the JSON-string form into an object, then wrap any bare-string host
value into a one-element array.
Invalid JSON is left in place so the downstream deserializer still
surfaces the error.

## Change Type
- [x] Bug fix

## How did you test this PR?

- `cargo nextest run -p agent-data-plane-config-system` (added
`additional_endpoints_accepts_json_string_scalar_or_map`, covering the
JSON-string, JSON-string-with-scalar, native-scalar, and native-list
shapes; confirmed it fails without the fix with `invalid type: string
..., expected a map`).
- `make fmt`

## References

- Merges into `m/pr5-cutover`
## Human Summary

Agents are thrashing hard on #1965 and they have a hard time
understanding that we can't fix it right now. I tried to get everything
else fixed.

## AI Summary

Cuts the Datadog forwarder subsystem over from the raw
`GenericConfiguration` map to the typed
`SalukiConfiguration` model. This is a tightly-coupled cluster, so it
moves together in one PR.

Static construction (no more source serde, no restated defaults):

- `ForwarderConfiguration`, `EndpointConfiguration`,
`ProxyConfiguration`, and `RetryConfiguration`
build field-by-field from `shared.endpoints` (and
`shared.metrics_encoding` for the V3 settings).
- `DatadogForwarderConfiguration` builds from the shared model; the
Cluster Agent forwarder and the
  `run.rs` call sites are rewired to pass typed slices.

Runtime refresh (now reads a `Live<SalukiConfiguration>` view instead of
a live
`GenericConfiguration`):

- `ResolvedEndpoint` refreshes its API key from a typed source: the
shared primary key, the
Multi-Region Failover override key, or the dual-shipping endpoint keys
by URL/index.
- `TransactionForwarder` and `ApiKeyValidator` thread the live view; the
retry policy's 403 secrets
gate reads it too. `ApiKeyValidator` re-validates when any API-key
source changes.

Config model:

- `secret_backend_command`,
`secret_refresh_on_api_key_failure_interval`, and `run_path` are
witnessed keys (in the vendored schema), promoted from `excluded:` to
`inventory:` and modeled as
`shared.secrets` and `shared.run_path`, with `consume_*` bodies in the
Datadog translator.

The `ForwarderConfiguration` and `ProxyConfiguration` config smoke tests
are retired; equivalent
coverage now lives in the construction and translator tests.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- `make build-schema-overlay && make fmt` (no changes)
- `make check-all`
- `make check-docs`
- `cargo nextest run` for `saluki-components` (`common::datadog`,
`forwarders`),
`agent-data-plane-config`, `agent-data-plane-config-system`, and the
`agent-data-plane`
  config/cli modules

## References

- Progresses #1788
- Merges into `m/pr5-cutover`
@webern webern requested a review from a team as a code owner July 5, 2026 20:02
Copilot AI review requested due to automatic review settings July 5, 2026 20:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates ADP logging configuration consumption from the raw GenericConfiguration map to the typed SalukiConfiguration/control.logging model, and updates the dynamic log-level watcher to follow a Live<String> view instead of watching raw config keys.

Changes:

  • Add translate_snapshot in agent-data-plane-config-system to produce a one-shot typed config snapshot for bootstrap-time consumers.
  • Update logging initialization/reload paths to translate raw config → typed snapshot → LoggingConfiguration.
  • Refactor DynamicLogLevelWorker to react to Live<String> updates of control.logging.level, and rewrite related unit tests to build the typed logging model directly.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/agent-data-plane-config-system/src/translators/datadog_translator.rs Adds translator tests validating logging schema defaults, driven values, and invalid byte-size error behavior.
lib/agent-data-plane-config-system/src/system.rs Introduces translate_snapshot for bootstrap-phase typed config translation with lenient error handling.
lib/agent-data-plane-config-system/src/lib.rs Re-exports translate_snapshot.
bin/agent-data-plane/src/main.rs Switches bootstrap logging setup to use a typed snapshot before building logging config.
bin/agent-data-plane/src/cli/run.rs Reloads logging after authoritative config arrives via typed snapshot translation.
bin/agent-data-plane/src/internal/mod.rs Threads &ConfigurationSystem into internal supervisor creation.
bin/agent-data-plane/src/internal/control_plane.rs Wires DynamicLogLevelWorker to a live view of control.logging.level and updates config-internal wiring.
bin/agent-data-plane/src/internal/logging.rs Refactors logging translation to accept the typed logging model; refactors dynamic log-level worker and tests.
bin/agent-data-plane/Cargo.toml Removes the serde_with dependency (no longer needed after refactor).
Cargo.lock Updates lockfile to reflect dependency removal.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +81 to +85
let log_file = if logging.disable_file_logging {
String::new()
} else if logging.file.is_empty() {
PlatformSettings::get_default_log_file_path()
.to_string_lossy()
Comment on lines +435 to +438
/// Flipping the level through the driven view wakes the worker's watch and yields the new first-party filter that
/// the worker feeds to the logging controller.
#[tokio::test]
async fn worker_reacts_to_live_log_level_change() {
@pr-commenter

pr-commenter Bot commented Jul 5, 2026

Copy link
Copy Markdown

Binary Size Analysis (Agent Data Plane)

Baseline: 7b98c6f · Comparison: a3eb0be · diff
Analysis Configuration: stripped binaries · Pass/Fail Threshold: +5%
Sizes: 41.63 MiB (baseline) vs 41.67 MiB (comparison)
Size Change: +38.75 KiB (+0.09%)

✅ Binary size difference within threshold

Changes by Module
Module File Size Symbols
figment -676.04 KiB 359
serde_json +245.15 KiB 507
core +191.64 KiB 9338
datadog_agent_config::generated::datadog_configuration +148.22 KiB 44
resource_accounting::groups::Tracked +121.38 KiB 35
tracing -116.85 KiB 98
serde -72.51 KiB 68
agent_data_plane_config_system::saluki_env_overlay::PathRecorder +66.09 KiB 24
tokio +66.07 KiB 3362
serde_with -44.76 KiB 24
saluki_components::common::datadog -40.83 KiB 529
saluki_components::sources::dogstatsd -39.88 KiB 368
agent_data_plane_config_system::translators::datadog_translator +31.90 KiB 25
prost +31.35 KiB 262
hyper_util +29.27 KiB 40
axum +28.80 KiB 330
agent_data_plane_config::SalukiConfiguration +28.67 KiB 1
alloc +27.29 KiB 1477
tonic -27.14 KiB 359
otlp_protos::otlp_include::opentelemetry -25.66 KiB 143
Detailed Symbol Changes
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  [NEW] +88.2Ki  [NEW] +87.8Ki    _<datadog_agent_config::generated::datadog_configuration::_::<impl serde_core::de::Deserialize for datadog_agent_config::generated::datadog_configuration::DatadogConfiguration>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map::h7af5cfb49708bfb8
  [NEW] +55.1Ki  [NEW] +54.9Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::h619cfc6f7a2c29a2
  [NEW] +46.4Ki  [NEW] +46.2Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::hb5fd858187688299
  [NEW] +40.9Ki  [NEW] +40.7Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h12001b40cfdbe9ad
  [NEW] +36.6Ki  [NEW] +36.4Ki    _<saluki_components::transforms::apm_stats::ApmStats as saluki_core::components::transforms::Transform>::run::_{{closure}}::hcfbaff7c8f04ca3a
  [NEW] +32.5Ki  [NEW] +32.3Ki    _<saluki_components::transforms::aggregate::Aggregate as saluki_core::components::transforms::Transform>::run::_{{closure}}::ha1b37f98738ba368
  [NEW] +32.1Ki  [NEW] +31.9Ki    saluki_components::sources::otlp::metrics::translator::OtlpMetricsTranslator::translate_metrics::h6f16bec49aa3e135
  [NEW] +30.3Ki  [NEW] +30.2Ki    agent_data_plane::cli::dogstatsd::handle_dogstatsd_command::_{{closure}}::h50ad6263ed4cc392
  [NEW] +28.7Ki  [NEW] +28.5Ki    _<agent_data_plane_config::SalukiConfiguration as core::clone::Clone>::clone::h8de0a5256ebf7df6
  [NEW] +27.3Ki  [NEW] +27.2Ki    agent_data_plane::cli::run::create_topology::_{{closure}}::h14989cb4ef506ffd
  [NEW] +26.9Ki  [NEW] +26.8Ki    saluki_components::sources::dogstatsd::drive_stream::_{{closure}}::h16dece6451b89a07
  [DEL] -25.6Ki  [DEL] -25.4Ki    saluki_components::sources::dogstatsd::drive_stream::_{{closure}}::h3fe2520e3abdfc38
  [DEL] -26.6Ki  [DEL] -26.4Ki    _<saluki_components::sources::dogstatsd::DogStatsDConfiguration as saluki_core::components::sources::builder::SourceBuilder>::build::_{{closure}}::h2c2a33d551977c6d
  [DEL] -27.2Ki  [DEL] -26.9Ki    _<saluki_components::sources::dogstatsd::_::<impl serde_core::de::Deserialize for saluki_components::sources::dogstatsd::DogStatsDConfiguration>::deserialize::__Visitor as serde_core::de::Visitor>::visit_map::hfc578ba92a99992a
  [DEL] -28.6Ki  [DEL] -28.5Ki    saluki_components::sources::otlp::metrics::translator::OtlpMetricsTranslator::translate_metrics::h1e8351a6f731e9df
  [DEL] -28.6Ki  [DEL] -28.5Ki    agent_data_plane::cli::dogstatsd::handle_dogstatsd_command::_{{closure}}::h84db3f7ad161eab2
  [DEL] -32.6Ki  [DEL] -32.4Ki    _<saluki_components::transforms::aggregate::Aggregate as saluki_core::components::transforms::Transform>::run::_{{closure}}::h72e4b09fd816d94f
  [DEL] -38.7Ki  [DEL] -38.5Ki    _<saluki_components::forwarders::otlp::OtlpForwarder as saluki_core::components::forwarders::Forwarder>::run::_{{closure}}::h3cd6ee67f0471508
  [DEL] -44.7Ki  [DEL] -44.6Ki    agent_data_plane::cli::run::handle_run_command::_{{closure}}::h83501a7881036c5f
  [DEL] -56.8Ki  [DEL] -56.6Ki    saluki_components::common::datadog::io::run_endpoint_io_loop::_{{closure}}::h54fbb73bb30184e6
  -0.6% -96.8Ki  -1.6%  -198Ki    [33865 Others]
  +0.1% +38.8Ki  -0.2% -63.0Ki    TOTAL

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e60f4ea20

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.to_string_lossy()
.into_owned(),
}
logging.file.clone()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve platform log-file defaults

When data_plane.log_file is absent, the typed Datadog model already contains the schema default /var/log/datadog/agent-data-plane.log, so this else branch treats that Linux path as an explicit configuration and never calls PlatformSettings::get_default_log_file_path(). On macOS and Windows, where PlatformSettings points at /opt/datadog-agent/logs or the registry-derived log directory, ADP now writes to the wrong default path unless the user overrides data_plane.log_file.

Useful? React with 👍 / 👎.

// (per-subagent log file key, never sharing a file with the Core Agent). The configuration system does not exist
// yet at this point, so translate a one-shot typed snapshot of the bootstrap configuration to read the model from.
let bootstrap_saluki_config =
agent_data_plane_config_system::translate_snapshot(&bootstrap_config, EnvOverlayMode::Fallback)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep permissive parsing for logging booleans

This bootstrap path now deserializes the whole config through the generated typed Datadog model before logging translation. The generated logging fields are plain bools, so values the previous read_permissive_bool path accepted, such as DD_LOG_TO_SYSLOG=1 or quoted YAML like log_to_console: "false", fail source deserialization and abort bootstrap instead of applying the logging setting.

Useful? React with 👍 / 👎.

@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Jul 5, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 5 Pipeline jobs failed

DataDog/saluki | test-integration-macos-arm64   View in Datadog   GitLab

DataDog/saluki | test-integration-windows-amd64   View in Datadog   GitLab

docs | generate-api-docs   View in Datadog   GitHub Actions

View all 5 failed jobs.

ℹ️ Info

🔄 Datadog auto-retried 5 jobs - 5 passed on retry View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: a3eb0be | Docs | Datadog PR Page | Give us feedback!

@pr-commenter

pr-commenter Bot commented Jul 5, 2026

Copy link
Copy Markdown

Regression Detector (Agent Data Plane)

Run ID: 4cb81ed2-aac4-4471-9ede-e6a2015a8778
Baseline: 7b98c6f1 · Comparison: a3eb0be3 · diff

Optimization Goals: ✅ No significant changes detected

Fine details of change detection per experiment (5)

Experiments configured erratic: true are tagged (ignored) and skipped when determining which experiments regressed or improved. Experiments which are detected as erratic at runtime are tagged (erratic) to flag that the run's sample dispersion was high, but their regression / improvement signal still counts.

experiment goal Δ mean % links
quality_gates_rss_dsd_low memory ⚪ +1.14 metrics profiles logs
quality_gates_rss_idle memory ⚪ +0.87 metrics profiles logs
quality_gates_rss_dsd_medium memory ⚪ +0.65 metrics profiles logs
quality_gates_rss_dsd_ultraheavy memory ⚪ +0.36 metrics profiles logs
quality_gates_rss_dsd_heavy memory ⚪ -0.02 metrics profiles logs
Bounds Checks: ✅ Passed (5)
experiment check replicates observed links
quality_gates_rss_dsd_heavy memory_usage 10/10 ✅ 140 MiB ≤ 140 MiB metrics profiles logs
quality_gates_rss_dsd_low memory_usage 10/10 ✅ 43.2 MiB ≤ 50 MiB metrics profiles logs
quality_gates_rss_dsd_medium memory_usage 10/10 ✅ 65.7 MiB ≤ 75 MiB metrics profiles logs
quality_gates_rss_dsd_ultraheavy memory_usage 10/10 ✅ 192 MiB ≤ 200 MiB metrics profiles logs
quality_gates_rss_idle memory_usage 10/10 ✅ 29.2 MiB ≤ 40 MiB metrics profiles logs
Explanation

A change is flagged as a regression when |Δ mean %| > 5.00% in the regressing direction for its optimization goal AND SMP marks the experiment as a regression (is_regression: true). Improvements use the matching criteria for the improving direction. Experiments configured erratic: true (tagged (ignored)) are skipped outright; experiments detected as erratic at runtime (tagged (erratic)) still count, since that flag describes sample dispersion rather than directional certainty. The Δ mean % cell is colored accordingly: 🟢 = improvement, 🔴 = regression, ⚪ = neutral. Reduction in CPU or memory is an improvement; reduction in ingress throughput is a regression.

)

## Summary

Cut the OTTL transform processor over from the raw
`GenericConfiguration` map to the typed
`SalukiConfiguration` model, following the pattern already applied to
the sibling OTTL filter
processor.

- `OttlTransformConfiguration::from_configuration` now takes the
resolved
`domains::traces::OttlTransform` settings and copies them directly,
instead of deserializing an
`OttlTransformConfig` out of `GenericConfiguration`. The component-local
config module (its
`ErrorMode` enum and `OttlTransformConfig` struct) is removed; the
component reuses the model's
  shared `OttlErrorMode`.
- The call site in `run.rs` reads
`saluki.domains.traces.ottl_transform`. Since this was the last
consumer of the raw config in
`add_baseline_traces_pipeline_to_blueprint`, the now-unused
  `GenericConfiguration` parameter is dropped from that function.
- The seed source `OttlTransformConfig` in `saluki_only.rs` is aligned
with the filter's: its
`error_mode` deserializes directly into `OttlErrorMode` (rather than a
loose `Option<String>` fed
through a hand-rolled parse helper) and it now rejects unknown fields,
restoring the strict
  deserialization the original component had.

`ottl_transform_config` is absent from the vendored Datadog schema, so
it is a Saluki-only key on
the seed track; there is no witnessed alias for it.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

- Rewrote the component tests to build from typed traces settings; all
OTTL transform tests pass.
- Added an `ottl_transform_config` round-trip rejection test mirroring
the filter's.
- `make build-schema-overlay` leaves the tree unchanged; `make fmt`,
`make check-all`, `make test`,
  and `make check-docs` all pass.

## References

- Progresses #1788
- Merges into #1987

@webern webern left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code, operating on behalf of @webern.

Clean-room review of the logging cutover. Overall this is a faithful migration: all eleven logging keys are witnessed with real consume_* bodies and land in control.logging, the syslog gating and default-URI fallback are preserved, the log-level parsing path is unchanged (parse_adp_log_level still routes plain levels through the first-party filter), and the test rewrites keep the behavioral coverage rather than just the deserialization. The Live<String> conversion for the dynamic worker is a clean equivalent of the old key watch.

Two things are worth resolving before this goes into the big-bang merge, plus a couple of low-severity notes.

1. Platform-specific default log-file path is lost on non-Linux

bin/agent-data-plane/src/internal/logging.rs still carries the PlatformSettings::get_default_log_file_path() fallback for an empty logging.file, but that branch is now unreachable in the default case.

data_plane.log_file carries a hardcoded schema default of /var/log/datadog/agent-data-plane.log (core_schema.yaml), so drive() populates logging.file with that Linux path even when the user configures nothing. logging.file is therefore never empty unless the user explicitly sets data_plane.log_file: "", and the platform fallback only fires in that one corner.

  • On Linux this is a no-op (same path).
  • On macOS (/opt/datadog-agent/logs) and Windows (C:\ProgramData\Datadog\logs, registry-derived), the pre-cutover code fell back to the platform path when the key was unset; it now writes the Linux path instead. That's a default regression.

Note the asymmetry with syslog_uri, whose schema default is empty (''), so its platform fallback still works correctly. The Core Agent's own log_file also defaults to ''. The cleanest fix is probably to give data_plane.log_file an empty default too (via the overlay/vendored schema), so the config layer keeps its "empty means use the platform default" contract and the existing translate fallback governs — rather than baking a single-platform literal into the model.

(Both Copilot and Codex flagged this independently. Copilot's comment references an adp-logging-windows-default-path integration test that doesn't exist in the tree, so ignore that specific citation — the underlying issue is real regardless.)

2. Dropped permissive-boolean coercion

The old component ran the logging booleans (log_format_json, log_to_console, log_to_syslog, syslog_rfc, disable_file_logging, etc.) through PermissiveBool; the typed model deserializes plain bool. Plain true/false still work (figment coerces those from env), but the forms PermissiveBool explicitly accepted — the short forms 1/0/t/T/f/F, and quoted-YAML strings like log_to_console: "false" — no longer deserialize, and at the bootstrap sites they'll fail DatadogConfiguration deserialization and abort startup rather than being coerced.

Logging was one of the few places that deliberately reached for PermissiveBool, so the question is whether that was load-bearing (mirroring the Agent's viper-style leniency for these keys) or belt-and-suspenders that's fine to drop for consistency with the other migrated bool keys. If it should be preserved, it needs hoisting to the source layer (e.g. alongside the existing normalize_datadog_input_forms coercions) rather than living in the component. Worth a conscious decision either way.

Minor notes (not blocking)

  • The two bootstrap sites go through the lenient translate_snapshot, which turns an invalid log_file_max_size into a warning and leaves file_max_size = 0 (→ ByteSize::b(0)). The pre-cutover component returned a hard error that aborted startup. This matches the config system's documented lenient-update contract, so it's likely intentional — just flagging that bootstrap no longer rejects a malformed byte size.
  • consume_log_file_max_rolls clamps negatives to 0 (value.max(0)) where the old try_get_typed::<usize> would have errored. Benign, and consistent with the project's integer handling.

The LoggingConfiguration field-by-field construction, syslog gating, and disable-file short-circuit all match the prior behavior. #1 is the one I'd want addressed before merge; #2 deserves an explicit decision.

webern added 3 commits July 6, 2026 12:22
## Human Summary

It thrashed a bit on preserving the exact behavior that `DurationString`
was providing and on viper `time.Duration` parsing correctness. Overall
the changes it made seem fine to me.

## AI Summary

Migrates `HostTagsConfiguration` from the raw `GenericConfiguration` map
to the typed
`SalukiConfiguration` model, continuing the ADP config cutover.

`expected_tags_duration` is witnessed and already modeled at
`shared.tags.expected_tags_duration`, so the component now reads it
directly from the typed
slice instead of parsing a `DurationString` off `GenericConfiguration`.
The component's
hand-written zero default is dropped; the generated schema default
(`0s`) is authoritative.

`RemoteAgentClientConfiguration` is a shared struct still consumed by
several not-yet-migrated
remote-agent components, so it stays on `GenericConfiguration` for now.
It is built at the call
site and passed into `HostTagsConfiguration`, which removes
`GenericConfiguration` from the
component entirely.

## Change Type
- [x] Non-functional (chore, refactoring, docs)

## How did you test this PR?

`make check-all`, `make test`, and `make check-docs` all pass.
Translation of
`expected_tags_duration` into the typed model is already covered by
existing translator and
config-system tests.

## References

- Progresses #1788
- Merges into #1987
Cut LoggingConfigurationTranslator and DynamicLogLevelWorker over from the
raw GenericConfiguration map to the typed SalukiConfiguration model.

The translator now builds LoggingConfiguration from control.logging (all
witnessed keys, driven from the vendored schema) rather than re-parsing raw
config, keeping only the ADP-specific business rules (first-party log-filter
expansion, syslog gating with platform-default fallback, per-subagent log-file
path). DynamicLogLevelWorker reacts to a Live<String> view of the log level.

Logging is set up before the configuration system exists, so add a lenient
one-shot translate_snapshot to the config-system crate and use it at the
bootstrap and pre-config-system reload sites.
… bools

Follow-up to the logging typed-config migration.

data_plane.log_file: flag the key saluki_overrides_default so its typed
default is empty instead of the Agent schema's Linux literal. An unset key
now leaves control.logging.file empty, which LoggingConfigurationTranslator
resolves to PlatformSettings::get_default_log_file_path() (correct on macOS
and Windows, not just Linux). An explicit path is preserved.

Logging permissive booleans: log_format_json, log_format_rfc3339,
log_to_console, log_to_syslog, syslog_rfc, and disable_file_logging were read
with saluki_common::deser::PermissiveBool before the migration. Coerce their
wider input forms back to native booleans in normalize_datadog_input_forms
(after apply_env_overlay, so env-var strings are covered too), scoped to just
these six keys. Invalid forms are left in place so the bool deserialize still
rejects them.
Copilot AI review requested due to automatic review settings July 6, 2026 10:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

webern added 2 commits July 6, 2026 12:57
… sentinel

Follow-up to the platform-log-file fix. `control.logging.file` used an empty
string as a sentinel for "unset, resolve the platform default", which the
`consume_data_plane_log_file` witness decoded from its `Option<String>` and the
component re-decoded via `is_empty()`. That routed the same "maybe unset" fact
through two representations.

Make the model type honest: `Logging.file` is now `Option<String>` (`None` =
unset). The witness assignment becomes a straight pass-through, and
`LoggingConfigurationTranslator` resolves `None` (or a blank path) to
`PlatformSettings::get_default_log_file_path()` via `unwrap_or_else`. No
behavior change: unset/blank still fall back to the platform default, an
explicit path still wins, and `disable_file_logging` still short-circuits.
…way snapshot

On the config-stream path, the logging reload built a full SalukiConfiguration
via `translate_snapshot` only to read `control.logging`, then discarded it —
moments before `ConfigurationSystem::load` deserialized and translated the same
map all over again. That was two full translations for one logging sub-tree.

Move the reload to just after the configuration system is loaded and read the
logging model straight from it. The reload now runs after the `!enabled` early
exit (so a disabled ADP no longer reloads logging on its way out) and before
topology/env-provider setup. `translate_snapshot` remains for the `main.rs`
bootstrap site, where no configuration system exists yet.
Copilot AI review requested due to automatic review settings July 6, 2026 11:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@webern webern marked this pull request as draft July 6, 2026 14:31
@webern webern force-pushed the m/pr6-adp-cutover branch 7 times, most recently from 47a5a4f to f638633 Compare July 10, 2026 10:26
@webern

webern commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Deferred

@webern webern closed this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants