Skip to content

feat(platform-api-and-sdk): first-party logging namespaces and complete ledger service outcomes (XWING-2440) - #320

Merged
smith-xyz merged 4 commits into
developmentfrom
feat/first-party-logging-namespaces-and-complete-ledg
Oct 31, 2026
Merged

smith-xyz merged 4 commits into
developmentfrom
feat/first-party-logging-namespaces-and-complete-ledg

Conversation

@smith-xyz

@smith-xyz smith-xyz commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Plan: plans/traust-standardization/8-platform-api-and-sdk/18-shaun-first-party-logging-namespaces.md

What

  • let Context.configure_logging(...) register explicit first-party logger roots while keeping traust_core mandatory and leaving Python's root and third-party loggers untouched
  • share the same secure formatter, correlation filter, selected outputs, rotation and private file handlers across registered roots; restore removed consumer logger state and repair managed state on repeated setup
  • expose correlation scopes through the public traust_core.v1.context API for consumer entry points
  • add safe structured ledger checkpoint, verify and audit outcomes; keep append/seal outcomes after commit and remove Merkle roots from their messages
  • emit only IDs, bounded counts, signing state, outcome and duration—never documents, signatures, key paths, identities, finding text or provider errors

Review resolutions

Three independent read-only reviews covered handler lifecycle/security, ledger transaction semantics, and regressions/tests.

  • handlers now receive the safe formatter/filter before attachment
  • retained logger roots remain non-propagating through reconfiguration, so concurrent records cannot escape into host handlers
  • idempotent setup repairs propagation and configured levels if another framework mutates them
  • ledger logging tests attach capture directly and restore logger state, so they pass deterministically in serial and xdist runs

Verification

  • make lint-fix
  • make test: 8,155 passed, 1 skipped
  • pre-push coverage: 96.72%, 8,155 passed, 1 skipped
  • SQLite/Postgres ledger service suite: 115 passed, 1 SQLite-only concurrency skip
  • no SQL or repository query changed; no perf run required

Reviewer checks (from the plan)

  • Can registering a sibling logger capture dependency logs or duplicate/compete for the rolling file? Can any ledger document, signature, key path, identity or provider error text escape?

Quality checklist

  • 1. Comments only for the non-trivial
  • 2. Docstrings don't bloat
  • 3. Don't reinvent
  • 4. Shared logic is a helper, in one place
  • 5. Tests cover the edges
  • 6. Integration tests are thorough and use real data, sanitized
  • 7. Minimal, clean code
  • 8. Queries are low latency, and you checked (no query changed)
  • 9. Tests change only on purpose, and a person said so

Legacy files

File Verdict Where it went / why it's gone
None reuse Extends the existing Context installer and LedgerService outcomes; no legacy path is replaced

Changed tests

Test Old → new Why
test_default_info_and_project_only_level_overrides only traust_core.* level overrides were accepted → renamed for first-party overrides and proves traust_engine.* is accepted while dependencies remain rejected consumer logger roots must use the same deployment-controlled levels without opening third-party logging
  • A person reviewed each changed test and the behaviour change is intended

Dependencies

This is the first PR in the logging follow-up chain. Ledger provider diagnostics and their Core integration are separate reviewable PRs; this PR does not pin or depend on those changes.

@smith-xyz
smith-xyz marked this pull request as ready for review October 30, 2026 19:38
@smith-xyz
smith-xyz requested a review from chubirka as a code owner October 30, 2026 19:38
@qodo-for-traust-security

Copy link
Copy Markdown

PR Summary by Qodo

Support first-party logging roots and safe ledger outcomes

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes


AI Description

• Register explicit first-party logger roots without changing host or third-party logging.
• Share secure handlers and correlation across roots, while restoring logger state during
 reconfiguration.
• Record privacy-safe ledger outcomes after commit and summarize verification without exposing
 findings.
Diagram

graph TD
  Context["Context API"] --> Installer["Logging installer"] --> Roots["Owned loggers"] --> Handlers["Safe handlers"] --> Outputs[("Private outputs")]
  Config["Logging config"] --> Installer
  Correlation["Correlation scopes"] --> Handlers
  Ledger["Ledger service"] --> Roots
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Configure Python's root logger
  • ➕ Simpler installation for all logger names.
  • ➖ Captures third-party records and risks changing host logging behavior.
2. Create separate handlers per namespace
  • ➕ Allows independent outputs and retention per consumer.
  • ➖ Competing rotating-file handlers complicate ownership and can duplicate records.

Recommendation: Keep the explicit root allowlist and shared handlers: they meet the isolation and single-file ownership requirements without taking control of host logging. Review the handler-swap and post-commit paths closely because those carry the main security and correctness risks.

Files changed (7) +423 / -52

Enhancement (4) +187 / -47
__init__.pyExport correlation helpers from the public context API +10/-0

Export correlation helpers from the public context API

• Re-exports Correlation and scope, lookup, and enrichment helpers so consumer entry points can use correlation without importing the provider module.

src/traust_core/v1/context/init.py

context.pyAccept consumer logger roots during logging setup +2/-2

Accept consumer logger roots during logging setup

• Extends Context.configure_logging to forward explicitly requested roots to the logging installer; traust_core remains included by the installer.

src/traust_core/v1/context/context.py

logging.pyShare secure logging handlers across managed roots +84/-24

Share secure logging handlers across managed roots

• Validates and deduplicates requested roots, installs formatted and filtered handlers before attachment, and shares them across selected namespaces. Reconfiguration restores removed roots, repairs retained logger settings, and preserves non-propagation during handler swaps; the formatter also includes bounded ledger fields.

src/traust_core/v1/context/logging.py

ledger.pyEmit bounded outcomes for ledger operations +91/-21

Emit bounded outcomes for ledger operations

• Adds structured outcome, duration, ID, signing-state, and count logging for append, seal, checkpoint, verify, and audit. Write outcomes remain post-commit, while audit uses an internal verification method to emit one terminal summary rather than a log per layer; Merkle roots and finding text are omitted.

src/traust_core/v1/services/ledger.py

Tests (2) +228 / -3
test_logging.pyCover consumer namespaces and safe handler reconfiguration +146/-3

Cover consumer namespaces and safe handler reconfiguration

• Tests shared outputs and correlation, root validation, level overrides, and restoration of removed roots. Additional tests check idempotent state repair and ensure records emitted during handler attachment or swapping do not leak exception details or reach host handlers.

tests/v1/context/test_logging.py

test_ledger.pyVerify ledger outcome timing and privacy +82/-0

Verify ledger outcome timing and privacy

• Adds direct logger capture and tests that checkpoint success is logged only after commit, failed checkpoints claim no success, and verify and audit emit bounded invalid summaries. Assertions check that sensitive values and finding text are absent.

tests/v1/services/test_ledger.py

Other (1) +8 / -2
config.pyAllow level overrides for recognized first-party namespaces +8/-2

Allow level overrides for recognized first-party namespaces

• Defines the permitted logger roots and validates level overrides against their exact names or descendants. Unrelated logger namespaces remain disallowed.

src/traust_core/v1/context/config.py

@qodo-for-traust-security

qodo-for-traust-security Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 🔗 Cross-repo conflicts (1) 📜 Skill insights (1)



View medium (5)
🟠 **Medium**
1. Failed signing looks like a routine append 🐞 Bug ⭐ New
Description
append_decision emits its post-commit outcome at the default INFO level even when
checkpoint.signing is Signing.FAILED. When signing is optional and an attempt fails, _sign
returns that status and the append is committed, but the emitted outcome gets neither the degraded
classification nor the warning severity used by the other signing operations.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An append whose optional signing attempt fails is logged as a routine committed INFO outcome despite returning a failed signing status.

## Fix Focus Areas
- src/traust_core/v1/services/ledger.py[154-165]
- tests/v1/services/test_ledger.py[373-403]

## Recommended Fix
Keep the post-commit timing and safe fields, but classify a failed signing attempt as degraded at WARNING severity. Preserve a distinct treatment for intentional unsigned appends if needed, and add regression coverage for an optional signing failure.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


2. Concurrent logging can duplicate events 🐞 Bug ⭐ New
Description
configure_logging attaches replacement handlers to retained logger roots before removing the
previous handlers. If another thread emits a record during that interval, both handler generations
can write it, including twice to the shared log file.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Concurrent emission during logging reconfiguration can write the same record through both old and new handlers.

## Fix Focus Areas
- src/traust_core/v1/context/logging.py[249-262]
- tests/v1/context/test_logging.py[90-127]

## Recommended Fix
Make replacement of managed handlers on retained roots avoid a period with both generations attached, while preserving safe handling of records emitted during the transition. Add a focused concurrent-emission regression test that asserts one output per record.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


3. A UUID in a log field breaks JSON logging 🐞 Bug ⭐ New
Description
_SafeFormatter.format now copies the record attributes repository_id, decision_id, layer_id,
count, errors and warnings into fields as-is, then calls json.dumps(fields) with no
default= fallback. This PR also sends records from traust, traust_engine and traust_ledger
through this formatter, so a consumer that logs extra={"repository_id": repo.id} with a real
UUID (the obvious way to pass an *_id) makes json.dumps raise TypeError. The record is then
lost, and Handler.handleError prints a traceback plus the raw message and args to stderr, which
skips the safe formatter entirely.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`_SafeFormatter.format` puts arbitrary record attributes (`repository_id`, `decision_id`, `layer_id`, `count`, `errors`, `warnings`) into a dict and calls `json.dumps` with no fallback. A non-JSON value such as a UUID raises `TypeError`, and logging's `handleError` then prints the raw message and args to stderr.

## Fix Focus Areas
- src/traust_core/v1/context/logging.py[111-123]

## Recommended Fix
Convert the new ID fields to `str` when they are not None (as `_CorrelationFilter` does). Allow only int/None for `count`, `errors` and `warnings`, or pass `default=str` to `json.dumps`. Add a test that logs `extra={"repository_id": uuid4()}` under the json format and asserts that one valid JSON line is written.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


4. Ledger documents enter managed logs 🔗 Cross-repo conflict ⭐ New
Description
Registering traust_ledger attaches core-managed handlers to ledger loggers, but _SafeFormatter
copies their rendered messages without removing sensitive content. In the mounted traust-ledger
revision, a malformed stored event reaches _parse_event, which logs the raw event dictionary and
sends it to the configured stderr or file output.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Opting the ledger namespace into core-managed logging captures raw event dictionaries emitted by the mounted ledger revision. Confirm whether the pinned ledger revision has the same warning before enabling this integration.

## Fix Focus Areas
- src/traust_core/v1/context/logging.py[249-256]
- src/traust_core/v1/context/logging.py[89-119]
- /cross_repos/traust-ledger/src/traust_ledger/_internal/gates.py[321-331]

## Recommended Fix
Coordinate with traust-ledger to replace raw-event warning values with safe identifiers or counts in the revision actually installed by core. Add a regression test that registers the ledger root, triggers a malformed stored event, and asserts that neither configured output contains its document content.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


5. Ledger outcomes accept unchecked names 📜 Skill insight
Description
The new _log_outcome helper accepts the finite operation and outcome values as plain str
parameters instead of typed enums. Every append, seal, audit, checkpoint and verify record passes
one of these closed sets through this helper, so misspelled values can reach structured logs without
validation.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
`_log_outcome` represents the finite ledger operation and outcome sets as unrestricted strings, allowing invalid values to reach structured logs.

Fix Focus Areas
- src/traust_core/v1/services/ledger.py[60-66]
- src/traust_core/v1/services/ledger.py[156-164]
- src/traust_core/v1/services/ledger.py[183-191]
- src/traust_core/v1/services/ledger.py[221-228]
- src/traust_core/v1/services/ledger.py[271-279]
- src/traust_core/v1/services/ledger.py[289-297]

Recommended Fix
Define StrEnum types for the finite ledger operation and outcome values, annotate `_log_outcome` with those enums, and pass enum members at each call site. Serialize the enum values when constructing the log message and structured fields.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



View low (1)
🟡 **Low**
6. Log levels for unregistered roots are silently dropped 🐞 Bug ⭐ New
Description
The config validator accepts level overrides for any first-party root (for example
traust_engine.x), but _levels keeps only the overrides under the roots passed to
configure_logging. Any other override is discarded without a warning. A consumer that sets
levels: {traust_engine.worker: DEBUG} and calls configure_logging() without registering
traust_engine gets a valid config whose setting never takes effect, and nothing reports it.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Level overrides that name a first-party root the caller did not register are dropped silently by `_levels`.

## Fix Focus Areas
- src/traust_core/v1/context/logging.py[190-205]

## Recommended Fix
In `configure_logging`, compare `config.levels` with the filtered `levels`. Raise `ConfigError` (or log a warning on `traust_core`) that lists the override names whose root was not registered.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Resolved findings
1. Secrets can escape during logger setup ✓ Resolved
Description
configure_logging makes a newly registered root non-propagating before attaching a safe handler,
and removes a retired root's handlers before restoring its propagation setting. If another thread
logs an error with exc_info during either handlerless interval, Python's fallback handler can
print the exception text instead of the sanitized record.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Registering or removing a consumer logger root briefly leaves it non-propagating and without a handler, allowing a concurrent exception-bearing record to reach Python's unsanitized fallback handler.

## Fix Focus Areas
- src/traust_core/v1/context/logging.py[238-253]

## Recommended Fix
Keep a safe handler attached throughout each root transition. Attach a configured safe handler before making a new root non-propagating, and ensure a removed root retains a safe or suppressing handler until its managed handlers and propagation state have been transitioned; do not rely on the configuration lock to block concurrent logging.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Comment on lines +60 to +66
def _log_outcome(
operation: str,
outcome: str,
duration_ms: float,
*,
level: int = logging.INFO,
**fields: object,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Medium

2. Ledger outcomes accept unchecked names 📜 Skill insight ≡ Correctness

The new _log_outcome helper accepts the finite operation and outcome values as plain str
parameters instead of typed enums. Every append, seal, audit, checkpoint and verify record passes
one of these closed sets through this helper, so misspelled values can reach structured logs without
validation.
Agent Prompt
Issue description
`_log_outcome` represents the finite ledger operation and outcome sets as unrestricted strings, allowing invalid values to reach structured logs.

Fix Focus Areas
- src/traust_core/v1/services/ledger.py[60-66]
- src/traust_core/v1/services/ledger.py[156-164]
- src/traust_core/v1/services/ledger.py[183-191]
- src/traust_core/v1/services/ledger.py[221-228]
- src/traust_core/v1/services/ledger.py[271-279]
- src/traust_core/v1/services/ledger.py[289-297]

Recommended Fix
Define StrEnum types for the finite ledger operation and outcome values, annotate `_log_outcome` with those enums, and pass enum members at each call site. Serialize the enum values when constructing the log message and structured fields.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment thread src/traust_core/v1/context/logging.py Resolved
@smith-xyz
smith-xyz force-pushed the feat/first-party-logging-namespaces-and-complete-ledg branch from 769e47e to f415aec Compare October 31, 2026 01:54
@smith-xyz
smith-xyz merged commit 35d599c into development Oct 31, 2026
6 checks passed
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.

1 participant