Skip to content

feat(continuous-ops): engine operational logging and correlation (XWING-2440) - #137

Merged
smith-xyz merged 7 commits into
traust-security:developmentfrom
smith-xyz:feat/engine-operational-logging-and-correlation
Nov 1, 2026
Merged

smith-xyz merged 7 commits into
traust-security:developmentfrom
smith-xyz:feat/engine-operational-logging-and-correlation

Conversation

@smith-xyz

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

Copy link
Copy Markdown
Contributor

Plan: plans/traust-standardization/0-continuous-ops/27-shaun-engine-operational-logging.md

What

  • pin merged Core development commit e01d420262641615427ecb537a14380c90442219 and configure traust_engine through the CLI's existing Core Context; library imports still configure no handlers
  • emit bounded startup/preflight, tick, lease, execution, record, diff and claim lifecycle outcomes through Core-selected JSON/text outputs
  • give each operation/tick a fresh correlation scope; add order/run IDs only within a nested execution scope and copy that scope into the heartbeat thread
  • remove argv and raw exception text from runner/record/heartbeat events; log only bounded reason/status/error-type codes, return code, counts and duration
  • replace repetitive stale/overdue/unrunnable/admission warning fanout with one terminal tick status while retaining the actionable unknown-lane summary
  • bump the feature release to 0.40.0 with changelog and lock metadata

Review resolutions

Three independent read-only reviews covered correctness/concurrency, sensitive-data flow and regression/release behavior.

  • nested execution correlation now restores the caller on every return/exception, so a later empty claim or unrelated record cannot inherit an old order/run ID
  • run/shadow router and observation setup now completes inside startup preflight before ready is emitted
  • all CLI operation boundaries catch unexpected Exception values, emit bounded failure records and return nonzero without printing raw traceback/exception text
  • Core completion rejection now emits exactly one engine.execution failed/completion_error event before re-raising; no execution path disappears between lease and CLI failure

Verification

  • make lint-fix
  • make test: 191 passed (xdist)
  • push hooks: lint, format and 191 tests passed
  • make check-release: release metadata valid against origin/main
  • no SQL or repository query changed; no perf run required

Reviewer checks (from the plan)

  • Can an expanded pack command, provider diagnostic or credential escape? Do correlation and terminal outcomes remain correct across loops, failures and the heartbeat thread without changing engine behavior?

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 Existing CLI, EngineLoop, Runner and record paths retain ownership; this adds their logging/correlation boundaries

Changed tests

Test Old → new Why
tests/test_engine_loop.py unchanged from development after rebase the merged base rejects unknown routed lanes; this PR retains that gate
  • A person reviewed the new logging and preflight regression tests

tests/test_operational_logging.py covers output selection, warning-only unhealthy ticks (including a real SQLite pass), failed stdout serialization, observation cleanup failures on run and shadow, bounded DEBUG frame locations, safe CLI errors, correlation reset, heartbeat propagation, secret suppression and startup preflight.

Dependencies / stack

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 30, 2026
@openshift-ci

openshift-ci Bot commented Oct 30, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: smith-xyz

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Oct 30, 2026
@smith-xyz
smith-xyz marked this pull request as ready for review October 30, 2026 20:05
@smith-xyz
smith-xyz requested a review from a team as a code owner October 30, 2026 20:05
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 30, 2026
@qodo-for-traust-security

Copy link
Copy Markdown

PR Summary by Qodo

Add correlated, bounded operational logging to the engine

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes


AI Description

• Route engine lifecycle events through Core-configured logging for consistent operational
 visibility.
• Correlate ticks and executions, including heartbeats, without logging commands or raw exceptions.
• Consolidate repetitive warnings into tick summaries and release the feature as 0.40.0.
Diagram

graph TD
  CLI["Engine CLI"] --> Preflight["Startup preflight"] --> Operations["Ticks and operations"] --> Scope["Execution scope"] --> Events["Lifecycle events"] --> Core["Core log outputs"]
  Scope --> Heartbeat["Heartbeat thread"] --> Events
Loading
High-Level Assessment

Using Core’s existing logging and correlation APIs is preferable to adding engine-specific handlers or passing IDs manually through every call. The shared lifecycle helper keeps event fields consistent while nested execution scopes restore the caller’s context.

Files changed (12) +746 / -71

Enhancement (4) +362 / -43
cli.pyConfigure Core logging and correlate CLI operation outcomes +115/-24

Configure Core logging and correlate CLI operation outcomes

• Configures logging through Core, assigns fresh startup, tick, and operation correlation IDs, and emits bounded lifecycle results. Completes loop setup before reporting ready and converts unexpected operation errors into nonzero exits without printing raw exception text.

src/traust_engine/ops/cli.py

events.pyAdd bounded lifecycle events and nested execution correlation +69/-0

Add bounded lifecycle events and nested execution correlation

• Introduces a common emitter for coded outcomes, counts, return codes, error types, and durations. Adds an order/run correlation scope that restores its caller’s context on exit.

src/traust_engine/ops/events.py

runner.pyCorrelate claimed executions and heartbeat failures +104/-12

Correlate claimed executions and heartbeat failures

• Reports lease, execution, completion, and lease-loss outcomes without logging argv or exception messages. Copies execution context into the heartbeat thread and emits a bounded failure if Core rejects completion.

src/traust_engine/ops/runner.py

shadow.pyReport bounded recorded-execution outcomes +74/-7

Report bounded recorded-execution outcomes

• Scopes recorded commands to their order IDs and logs completion or failure using bounded fields. Replaces raw process-error logging and reports completion rejection before re-raising.

src/traust_engine/ops/shadow.py

Refactor (2) +1 / -18
admission.pyRemove admission warning fanout +0/-7

Remove admission warning fanout

• Stops logging withheld and deferred totals separately; the CLI now includes them in its terminal tick status.

src/traust_engine/ops/admission.py

controller.pyConsolidate repetitive routing and tick warnings +1/-11

Consolidate repetitive routing and tick warnings

• Removes per-condition stale, overdue, unrunnable, and routing warnings in favor of aggregate tick status. Retains the actionable unknown-lane summary.

src/traust_engine/ops/controller.py

Tests (2) +367 / -1
test_engine_loop.pyAdapt unknown-lane warning capture +12/-1

Adapt unknown-lane warning capture

• Attaches the test capture handler directly to the controller logger so the retained unknown-lane warning remains asserted under Core logging configuration.

tests/test_engine_loop.py

test_operational_logging.pyTest output selection, correlation, and sensitive-data boundaries +355/-0

Test output selection, correlation, and sensitive-data boundaries

• Adds integration coverage for Core-selected outputs, tick summaries, execution-scope restoration, and heartbeat context propagation. Tests preflight, command, completion, and operation failures using synthetic secrets to verify bounded logs and stderr.

tests/test_operational_logging.py

Documentation (1) +7 / -0
CHANGELOG.mdDocument the 0.40.0 logging release +7/-0

Document the 0.40.0 logging release

• Describes Core-configured lifecycle logging and heartbeat correlation, including the exclusion of commands, credentials, process output, and raw exceptions.

CHANGELOG.md

Other (3) +9 / -9
VERSIONSet release version to 0.40.0 +1/-1

Set release version to 0.40.0

• Advances the engine’s release version from 0.39.4.

VERSION

pyproject.tomlBump engine version and pin the logging-capable Core commit +2/-2

Bump engine version and pin the logging-capable Core commit

• Sets package version 0.40.0 and pins the Core integration commit used for logging and correlation.

pyproject.toml

uv.lockSynchronize locked release and Core dependency revisions +6/-6

Synchronize locked release and Core dependency revisions

• Updates the locked engine version and Core commit, along with resolved traust-contracts and traust-ledger commits.

uv.lock

@qodo-for-traust-security

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

Copy link
Copy Markdown

Code Review by Qodo


Resolved findings
1. Unexpected crashes leave no traceback to debug ✓ Resolved
Description
main now catches every Exception at the startup, tick and operation boundaries and records only
type(error).__name__ in both stderr and the log, with no exc_info anywhere. Before, only
OSError/TraustError were caught and anything else reached the user as a full traceback. Now a
programming error (KeyError, AttributeError, a Core bug) in a long-running run loop or in claim
shows up only as tick failed (KeyError), with no file, line or message to find the cause.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The broad `except Exception` handlers drop the stack trace entirely, so unexpected errors in the engine cannot be diagnosed.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[289-311]
- src/traust_engine/ops/events.py[30-69]

## Recommended Fix
For exceptions that are not expected `TraustError`/`OSError` types, emit a DEBUG-level record (or a separate record) with the frame locations only: file, line and function names from `traceback.extract_tb`, but no exception message or argv. A narrower alternative is to catch only the expected types and let real bugs propagate with their traceback.

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


2. Shadow ticks hide stale work warnings ✓ Resolved
Description
_passes reduces engine.shadow_once() to its due orders and logs only their count, while this
change removes the warnings for unrunnable assets, stale upstreams, and overdue subjects. When any
of those conditions occurs during a shadow tick, the diagnostics remain in ShadowReport.tick but
appear in neither the terminal lifecycle event nor the CLI's reduced JSON report.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Shadow ticks discard diagnostic counts from `ShadowReport.tick` after the corresponding controller warnings were removed, leaving operators unable to see unrunnable, stale, or overdue work.
## Fix Focus Areas
- src/traust_engine/ops/cli.py[95-97]
- src/traust_engine/ops/controller.py[101-135]
## Recommended Fix
Keep the full `shadow_once()` result and include bounded unrunnable, stale, and overdue counts from its `tick` in the shadow terminal lifecycle status. Preserve the current CLI JSON shape if it is a compatibility contract.

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


3. Operators no longer see why a command was rejected ✓ Resolved
Description
_error prints only type(error).__name__, and the lifecycle records log only error_type, so
first-party messages such as `ConfigError("give the command after --, and --repo exactly when the
asset has one"), NotFoundError("<repo> is not in the inventory")` and pack/core YAML validation
errors are no longer shown anywhere. Before this change these were printed as ERROR: {e}. Now
every misconfigured record/claim/diff/startup call prints only something like `ERROR: record
failed (ConfigError)`, and the operator cannot tell which flag, asset, repository or config key is
wrong.
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CLI now prints only the exception class name. Actionable first-party error messages (ConfigError, NotFoundError, validation errors) are lost, so operators cannot see what to fix.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[246-247]
- src/traust_engine/ops/cli.py[256-320]

## Recommended Fix
Keep the bounded logging. In `_error`, though, print `str(error)` to stderr for engine-authored `TraustError` subclasses (ConfigError, NotFoundError), because their messages contain no command or credential data. Keep printing only the type for other exceptions. Alternatively, raise these with a bounded, safe `reason` code and print that.

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



Comment thread src/traust_engine/ops/cli.py Resolved
Comment thread src/traust_engine/ops/cli.py Outdated
Comment on lines +246 to +247
def _error(operation: str, error: Exception) -> None:
print(f"ERROR: {operation} failed ({type(error).__name__})", file=sys.stderr)

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. Operators no longer see why a command was rejected 🐞 Bug ⚙ Maintainability

_error prints only type(error).__name__, and the lifecycle records log only error_type, so
first-party messages such as `ConfigError("give the command after --, and --repo exactly when the
asset has one"), NotFoundError("<repo> is not in the inventory")` and pack/core YAML validation
errors are no longer shown anywhere. Before this change these were printed as ERROR: {e}. Now
every misconfigured record/claim/diff/startup call prints only something like `ERROR: record
failed (ConfigError)`, and the operator cannot tell which flag, asset, repository or config key is
wrong.
Agent Prompt
## Issue description
The CLI now prints only the exception class name. Actionable first-party error messages (ConfigError, NotFoundError, validation errors) are lost, so operators cannot see what to fix.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[246-247]
- src/traust_engine/ops/cli.py[256-320]

## Recommended Fix
Keep the bounded logging. In `_error`, though, print `str(error)` to stderr for engine-authored `TraustError` subclasses (ConfigError, NotFoundError), because their messages contain no command or credential data. Keep printing only the type for other exceptions. Alternatively, raise these with a bounded, safe `reason` code and print that.

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

Dismiss ↗ | View ↗

Comment on lines +290 to +294
try:
return _passes(args, core, engine, shadow=args.op == "shadow")
except Exception as error:
_error("tick", error)
return 2

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

3. Unexpected crashes leave no traceback to debug 🐞 Bug ◔ Observability

main now catches every Exception at the startup, tick and operation boundaries and records only
type(error).__name__ in both stderr and the log, with no exc_info anywhere. Before, only
OSError/TraustError were caught and anything else reached the user as a full traceback. Now a
programming error (KeyError, AttributeError, a Core bug) in a long-running run loop or in claim
shows up only as tick failed (KeyError), with no file, line or message to find the cause.
Agent Prompt
## Issue description
The broad `except Exception` handlers drop the stack trace entirely, so unexpected errors in the engine cannot be diagnosed.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[289-311]
- src/traust_engine/ops/events.py[30-69]

## Recommended Fix
For exceptions that are not expected `TraustError`/`OSError` types, emit a DEBUG-level record (or a separate record) with the frame locations only: file, line and function names from `traceback.extract_tb`, but no exception message or argv. A narrower alternative is to catch only the expected types and let real bugs propagate with their traceback.

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

Dismiss ↗ | View ↗

@chubirka chubirka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code review: changes required

Reviewed commit 1f280b541df9acd76768e47c6a518d4a5e54349d. Found 1 actionable issue(s); details are attached as inline review comments.

Merge hold: keep this PR in draft with auto-merge disabled until the findings have been addressed and the affected behavior has been re-reviewed. Do not mark ready solely because unrelated CI passes.

Comment thread src/traust_engine/ops/cli.py Outdated
error_type=type(error).__name__,
)
raise
lifecycle(log, "tick", "completed", started=started, status=status)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P2] Preserve warning severity for unhealthy tick summaries

The replacement tick summary always uses outcome="completed", so lifecycle emits it at INFO even when stale/overdue/unrunnable counts (or run admission withheld/deferred counts) are nonzero. This PR removes the corresponding WARNING calls from Controller and Admission. Consequently an operator using logging.level=WARNING, or a WARNING override for traust_engine, loses the existing alerts that work cannot run or has exceeded its freshness deadline. Keep the single bounded summary, but select WARNING when it contains the conditions that previously generated warnings; use INFO only for healthy ticks.

Evidence: Pinned base 779471d has WARNING emissions for unrunnable assets (controller.py:104), stale subjects (:131), overdue subjects (:173), and withheld/deferred admission (admission.py:101); the PR deletes them. At pinned head, cli.py:129 supplies no level and events.py:45-47 selects INFO for completed outcomes. A real shadow CLI invocation with a fresh SQLite store produced stale={downstream:1}, overdue={downstream:1}, unrunnable=[disabled]. With INFO configured its only tick event was INFO; with WARNING configured the entire log file contained zero events, although the same unhealthy state persisted.

Verification: Executed OperationalLogging-warning-repro.py against the pinned engine/Core/ledger stack with actual CLI, Pack, EngineLoop, and SQLite (no monkeypatches). Both invocations exited 0; INFO recorded engine.startup plus the unhealthy engine.tick; WARNING recorded 0 events. The 52 focused engine tests pass but do not test degraded tick severity.

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.

Addressed in be03c01 after rebasing onto merged engine #132/#133. The single terminal tick event is now WARNING when stale, overdue, unrunnable, withheld or deferred counts are nonzero, and INFO only when healthy; its status retains bounded counts and asset identifiers. A real SQLite run with a disabled asset and WARNING-only logging, plus shadow and withheld-admission regressions, now prove the warning is visible. The preflight gate remains inside startup before ready; I have kept this PR draft and your CHANGES_REQUESTED review hold pending re-review.

@chubirka
chubirka marked this pull request as draft October 31, 2026 01:29
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 31, 2026
@smith-xyz
smith-xyz marked this pull request as ready for review November 1, 2026 01:31
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 1, 2026
@qodo-code-review

qodo-code-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Observation cleanup leaks a traceback ✓ Resolved
Description
main registers _loop with ExitStack, but the stack exits outside the try that handles tick
failures. If core.repository_observation() raises while closing after a run or shadow pass, the
exception bypasses _error and exits the CLI with a raw traceback instead of bounded failure
output.
Code

src/traust_engine/ops/cli.py[325]

+    with ExitStack() as stack:
Evidence
_loop keeps the repository-observation context open across its yield. main registers _loop
with the stack, but the tick handler catches only exceptions raised while _passes runs; stack
cleanup occurs after that handler has finished. Code inspection establishes that an exit-time
exception has no CLI handler.

src/traust_engine/ops/cli.py[74-84]
src/traust_engine/ops/cli.py[325-343]
src/traust_engine/ops/cli.py[362-367]

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 exception raised while the run/shadow observation context closes escapes the CLI's bounded error handling and prints a raw traceback.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[325-367]
- src/traust_engine/ops/cli.py[74-84]

## Recommended Fix
Place the `ExitStack` exit inside an exception boundary that emits a bounded cleanup-failure event, reports a safe error, and returns a nonzero exit code. Add a regression test with an observation context that raises on exit, asserting the exit code and absence of raw exception text or traceback.

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



Remediation recommended

2. Failed tick output still logs completion ✓ Resolved
Description
_passes emits the tick’s completed lifecycle event before serializing and printing its report,
outside the tick failure handler. If stdout serialization, writing, or flushing fails, the outer CLI
handler returns exit code 2 without a tick failure event, leaving a completed event for a tick whose
required output was not delivered.
Code

src/traust_engine/ops/cli.py[129]

+            lifecycle(log, "tick", "completed", started=started, status=status)
Evidence
The new completion call precedes print(report.model_dump_json(), flush=True) and lies after the
inner exception handler. The outer handler catches the resulting exception but only prints a bounded
error and returns 2; it emits no replacement tick event.

src/traust_engine/ops/cli.py[119-132]
src/traust_engine/ops/cli.py[297-302]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tick logs completion before its JSON report is successfully written, so an output failure leaves a misleading terminal event.
## Fix Focus Areas
- src/traust_engine/ops/cli.py[94-130]
## Recommended Fix
Serialize and print the report within the tick try block, then emit `completed` only after the print succeeds. Let the existing exception handler emit the bounded tick failure event for serialization and stdout errors. Add a regression test that makes stdout writing fail and checks the failure event and nonzero exit.

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


3. CLI errors no longer tell the operator what to fix ✓ Resolved
Description
_error prints only ERROR: <op> failed (<ExceptionType>), and every startup and operation failure
in main now goes through it, so engine-written messages such as "give the command after --, and
--repo exactly when the asset has one", "<repo> is not in the inventory", "pack X has no asset Y",
or a bad --core/--pack path are dropped. A mistyped flag or missing file now gives an operator
just ConfigError/NotFoundError/FileNotFoundError on stderr, and the structured log carries
only error_type, so the actionable reason is nowhere.
Code

src/traust_engine/ops/cli.py[R254-255]

+def _error(operation: str, error: Exception) -> None:
+    print(f"ERROR: {operation} failed ({type(error).__name__})", file=sys.stderr)
Evidence
_record raises ConfigError/NotFoundError with messages the engine itself wrote, which hold no
secrets. The old handler printed ERROR: {e}. The new _error prints only the type name, at both
the startup and the operation boundary in main.

src/traust_engine/ops/cli.py[136-148]
src/traust_engine/ops/cli.py[254-330]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CLI failures now print only the exception class, so operators lose the reasons the engine writes for itself (missing --repo, unknown asset, inventory miss, missing file).

## Fix Focus Areas
- src/traust_engine/ops/cli.py[254-256]
- src/traust_engine/ops/cli.py[136-148]

## Recommended Fix
Add a small allow-list of safe error types, such as engine-raised ConfigError/NotFoundError and argument-validation errors, whose messages come from the engine. Print those messages on stderr, or attach a bounded `reason` code per raise site. Keep type-only output for unknown exceptions. Add a test asserting the ConfigError guidance text reaches stderr.

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


4. Freshness failures are now logged as routine info ✓ Resolved
Description
The per-asset log.warning calls for freshness.fail overdue subjects, stale upstreams, unrunnable
assets and admission withheld/deferred are removed. Their only replacement is the tick completed
record, which lifecycle emits at INFO with counts only. An alert keyed on WARNING no longer fires
when subjects pass freshness.fail or an asset cannot run, and the asset names (bounded,
non-sensitive pack identifiers) are no longer logged anywhere.
Code

src/traust_engine/ops/cli.py[129]

+            lifecycle(log, "tick", "completed", started=started, status=status)
Evidence
lifecycle picks INFO for the 'completed' outcome unless a level is passed. _passes calls it with
no level, whatever the overdue/stale/unrunnable/withheld counts are. The removed controller and
admission warnings were the only WARNING-level signal for these states and included asset names.

src/traust_engine/ops/events.py[44-47]
src/traust_engine/ops/cli.py[99-129]
src/traust_engine/ops/controller.py[160-168]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tick summary is always INFO, even when subjects are overdue, upstreams are stale, assets are unrunnable or admission is withheld or deferred, so warning-level alerting loses these signals.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[99-129]
- src/traust_engine/ops/events.py[44-47]

## Recommended Fix
In `_passes`, pass `level=logging.WARNING` (and perhaps `reason="degraded"`) to `lifecycle` when overdue, stale, unrunnable, withheld or deferred is non-zero. Consider including the sorted overdue and unrunnable asset names, capped in length, in the status. Add tests for both levels.

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



Informational

5. Pinned Core commit differs from the one described ⊘ Outdated
Description
pyproject.toml and uv.lock pin traust-core at ffa462ef4e50dbcf736bfec8f1d685bbd93ab4b4, but
the PR description says the reviewed Core integration commit is
a2099a6153b6e236c196c1ee504b7147fa0db942. Reviewers checking the Core
configure_logging/correlation_scope redaction behaviour against the described commit would be
checking different code from what ships.
Code

pyproject.toml[23]

+traust-core = { git = "https://github.com/traust-security/traust-core.git", rev = "ffa462ef4e50dbcf736bfec8f1d685bbd93ab4b4" }
Evidence
The diff pins ffa462ef… in both pyproject and uv.lock. The PR description names a2099a6… as the
pinned commit. The lockfile revision also dropped from 5 to 3, which suggests an older uv wrote
it.

pyproject.toml[22-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The traust-core pin in pyproject/uv.lock does not match the commit the PR description says was reviewed.

## Fix Focus Areas
- pyproject.toml[23-23]

## Recommended Fix
Check which Core commit holds the reviewed secure logging handlers. Then either update the pin or update the PR description and changelog, and regenerate uv.lock with the team's standard uv version.

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


Grey Divider

Context sources
✅ Linked PRs:
  🌐 PR #133
❌ Tickets: failed to load — Ticket collection could not establish complete evidence
Review mode: Auto: ⚖️ Balanced: Exception-boundary and bounded operational logging changes carry real safety risk.
✅ Fix-loop round: fix loop

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 1df5d9e

Results up to commit 1f280b5 🧠 Deep


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. CLI errors no longer tell the operator what to fix ✓ Resolved
Description
_error prints only ERROR: <op> failed (<ExceptionType>), and every startup and operation failure
in main now goes through it, so engine-written messages such as "give the command after --, and
--repo exactly when the asset has one", "<repo> is not in the inventory", "pack X has no asset Y",
or a bad --core/--pack path are dropped. A mistyped flag or missing file now gives an operator
just ConfigError/NotFoundError/FileNotFoundError on stderr, and the structured log carries
only error_type, so the actionable reason is nowhere.
Code

src/traust_engine/ops/cli.py[R254-255]

+def _error(operation: str, error: Exception) -> None:
+    print(f"ERROR: {operation} failed ({type(error).__name__})", file=sys.stderr)
Evidence
_record raises ConfigError/NotFoundError with messages the engine itself wrote, which hold no
secrets. The old handler printed ERROR: {e}. The new _error prints only the type name, at both
the startup and the operation boundary in main.

src/traust_engine/ops/cli.py[136-148]
src/traust_engine/ops/cli.py[254-330]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CLI failures now print only the exception class, so operators lose the reasons the engine writes for itself (missing --repo, unknown asset, inventory miss, missing file).

## Fix Focus Areas
- src/traust_engine/ops/cli.py[254-256]
- src/traust_engine/ops/cli.py[136-148]

## Recommended Fix
Add a small allow-list of safe error types, such as engine-raised ConfigError/NotFoundError and argument-validation errors, whose messages come from the engine. Print those messages on stderr, or attach a bounded `reason` code per raise site. Keep type-only output for unknown exceptions. Add a test asserting the ConfigError guidance text reaches stderr.

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


2. Failed tick output still logs completion ✓ Resolved
Description
_passes emits the tick’s completed lifecycle event before serializing and printing its report,
outside the tick failure handler. If stdout serialization, writing, or flushing fails, the outer CLI
handler returns exit code 2 without a tick failure event, leaving a completed event for a tick whose
required output was not delivered.
Code

src/traust_engine/ops/cli.py[129]

+            lifecycle(log, "tick", "completed", started=started, status=status)
Evidence
The new completion call precedes print(report.model_dump_json(), flush=True) and lies after the
inner exception handler. The outer handler catches the resulting exception but only prints a bounded
error and returns 2; it emits no replacement tick event.

src/traust_engine/ops/cli.py[119-132]
src/traust_engine/ops/cli.py[297-302]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tick logs completion before its JSON report is successfully written, so an output failure leaves a misleading terminal event.
## Fix Focus Areas
- src/traust_engine/ops/cli.py[94-130]
## Recommended Fix
Serialize and print the report within the tick try block, then emit `completed` only after the print succeeds. Let the existing exception handler emit the bounded tick failure event for serialization and stdout errors. Add a regression test that makes stdout writing fail and checks the failure event and nonzero exit.

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


3. Freshness failures are now logged as routine info ✓ Resolved
Description
The per-asset log.warning calls for freshness.fail overdue subjects, stale upstreams, unrunnable
assets and admission withheld/deferred are removed. Their only replacement is the tick completed
record, which lifecycle emits at INFO with counts only. An alert keyed on WARNING no longer fires
when subjects pass freshness.fail or an asset cannot run, and the asset names (bounded,
non-sensitive pack identifiers) are no longer logged anywhere.
Code

src/traust_engine/ops/cli.py[129]

+            lifecycle(log, "tick", "completed", started=started, status=status)
Evidence
lifecycle picks INFO for the 'completed' outcome unless a level is passed. _passes calls it with
no level, whatever the overdue/stale/unrunnable/withheld counts are. The removed controller and
admission warnings were the only WARNING-level signal for these states and included asset names.

src/traust_engine/ops/events.py[44-47]
src/traust_engine/ops/cli.py[99-129]
src/traust_engine/ops/controller.py[160-168]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tick summary is always INFO, even when subjects are overdue, upstreams are stale, assets are unrunnable or admission is withheld or deferred, so warning-level alerting loses these signals.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[99-129]
- src/traust_engine/ops/events.py[44-47]

## Recommended Fix
In `_passes`, pass `level=logging.WARNING` (and perhaps `reason="degraded"`) to `lifecycle` when overdue, stale, unrunnable, withheld or deferred is non-zero. Consider including the sorted overdue and unrunnable asset names, capped in length, in the status. Add tests for both levels.

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



Informational
4. Pinned Core commit differs from the one described ⊘ Outdated
Description
pyproject.toml and uv.lock pin traust-core at ffa462ef4e50dbcf736bfec8f1d685bbd93ab4b4, but
the PR description says the reviewed Core integration commit is
a2099a6153b6e236c196c1ee504b7147fa0db942. Reviewers checking the Core
configure_logging/correlation_scope redaction behaviour against the described commit would be
checking different code from what ships.
Code

pyproject.toml[23]

+traust-core = { git = "https://github.com/traust-security/traust-core.git", rev = "ffa462ef4e50dbcf736bfec8f1d685bbd93ab4b4" }
Evidence
The diff pins ffa462ef… in both pyproject and uv.lock. The PR description names a2099a6… as the
pinned commit. The lockfile revision also dropped from 5 to 3, which suggests an older uv wrote
it.

pyproject.toml[22-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The traust-core pin in pyproject/uv.lock does not match the commit the PR description says was reviewed.

## Fix Focus Areas
- pyproject.toml[23-23]

## Recommended Fix
Check which Core commit holds the reviewed secure logging handlers. Then either update the pin or update the PR description and changelog, and regenerate uv.lock with the team's standard uv version.

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


Results up to commit be03c01 🧠 Deep


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Observation cleanup leaks a traceback ✓ Resolved
Description
main registers _loop with ExitStack, but the stack exits outside the try that handles tick
failures. If core.repository_observation() raises while closing after a run or shadow pass, the
exception bypasses _error and exits the CLI with a raw traceback instead of bounded failure
output.
Code

src/traust_engine/ops/cli.py[325]

+    with ExitStack() as stack:
Evidence
_loop keeps the repository-observation context open across its yield. main registers _loop
with the stack, but the tick handler catches only exceptions raised while _passes runs; stack
cleanup occurs after that handler has finished. Code inspection establishes that an exit-time
exception has no CLI handler.

src/traust_engine/ops/cli.py[74-84]
src/traust_engine/ops/cli.py[325-343]
src/traust_engine/ops/cli.py[362-367]

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 exception raised while the run/shadow observation context closes escapes the CLI's bounded error handling and prints a raw traceback.

## Fix Focus Areas
- src/traust_engine/ops/cli.py[325-367]
- src/traust_engine/ops/cli.py[74-84]

## Recommended Fix
Place the `ExitStack` exit inside an exception boundary that emits a bounded cleanup-failure event, reports a safe error, and returns a nonzero exit code. Add a regression test with an observation context that raises on exit, asserting the exit code and absence of raw exception text or traceback.

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


Grey Divider

Qodo Logo

@smith-xyz
smith-xyz force-pushed the feat/engine-operational-logging-and-correlation branch from 1f280b5 to be03c01 Compare November 1, 2026 03:17
@smith-xyz

Copy link
Copy Markdown
Contributor Author

Review follow-up at be03c01 (rebased onto engine development 4999f3c): (1) stdout serialization/write now completes before a tick can log completed; failure emits only a failed tick, with a regression. (2) Preflight errors retain actionable unmet-requirement details; CLI-authored validation and missing-asset errors give bounded flag/inventory guidance without echoing arbitrary exception text, commands or credentials. (3) unexpected failures emit DEBUG-only package file/line/function frames, never raw exception messages or tracebacks. (4) stale/overdue/unrunnable and withheld/deferred ticks now emit one WARNING summary (bounded asset identifiers); healthy ticks remain INFO. Verified a real SQLite WARNING-only run and focused shadow/admission cases. (5) Core is pinned in pyproject.toml, uv.lock and the updated PR description to reachable merged e01d4202, not the old orphaned/reviewed SHA. Rebase preserved #133 startup preflight and #132 runner/routing behavior. make lint-fix, make check-release, full make test (189 passed), and push hooks passed. This remains draft with Michele’s CHANGES_REQUESTED hold; Qodo attribution was not changed automatically.

@smith-xyz
smith-xyz requested a review from chubirka November 1, 2026 03:18
@smith-xyz
smith-xyz marked this pull request as draft November 1, 2026 03:18
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 1, 2026
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit be03c01

@smith-xyz
smith-xyz marked this pull request as ready for review November 1, 2026 19:29
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 1, 2026
@smith-xyz

Copy link
Copy Markdown
Contributor Author

Qodo’s new “Observation cleanup leaks a traceback” finding is fixed at 1df5d9e. I reproduced it first: both run --once and shadow --once raised a raw RuntimeError when the repository-observation context failed on exit. The CLI now catches the ExitStack exit at its outer boundary, records one bounded engine.observation failed / cleanup_error event, prints only the exception type and returns 2. Both regressions now pass and assert no traceback or secret in stderr/logs. make lint-fix, make check-release, full make test (191 passed), and push hooks passed; the PR description reflects the new coverage. Michele’s CHANGES_REQUESTED re-review remains pending, and CI for this head is running.

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1df5d9e

@smith-xyz
smith-xyz merged commit 7db1075 into traust-security:development Nov 1, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants