Skip to content

Isolate tracing from wrap domain logic (#309) - #412

Open
lodyai[bot] wants to merge 4 commits into
mainfrom
issue-309-isolate-vendor-specific-tracing-from-domain-logic-behind-adapter-boundaries
Open

Isolate tracing from wrap domain logic (#309)#412
lodyai[bot] wants to merge 4 commits into
mainfrom
issue-309-isolate-vendor-specific-tracing-from-domain-logic-behind-adapter-boundaries

Conversation

@lodyai

@lodyai lodyai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • introduce domain-owned classification events and observers
  • translate events through a tracing adapter at the wrapping boundary
  • remove vendor-specific tracing imports and guards from inline and tokenizer domain modules

Closes #309

Validation

  • make check-fmt
  • make lint
  • make typecheck
  • make test
  • coderabbit review --agent

References

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @LodyAI[bot], you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary

  • Introduced an observer boundary for inline classification and tokenisation, decoupling domain logic from tracing.
  • Added borrowed domain events, ObserverHandle, and a TracingObserver adapter with deferred, gated derived work.
  • Added footnote-reference, link/image, date-sequence, and fragment-classification events with expanded tests.
  • Added ADR 0006, architecture guidance, and developer documentation.
  • Added Criterion benchmarks and fixtures comparing disabled observers with tracing observers without an enabled subscriber.
  • Removed vendor-specific tracing instrumentation from inline and tokenizer modules while preserving existing wrapping behaviour.
  • Updated benchmark and property-test coverage; closes issue #309.

Walkthrough

Route inline tokenisation, span construction, and fragment classification through an observer event model. Adapt tracing through TracingObserver, add feature-gated observer benchmarks and fixtures, and document the new boundary.

Changes

Inline observer pipeline

Layer / File(s) Summary
Observer contract and tracing adapter
src/wrap.rs, src/wrap/observer.rs, src/wrap/tracing_adapter.rs
Define domain events, observer handles, fragment categories, and tracing translation.
Observed tokenisation and parsing
src/wrap/tokenize/*, src/wrap/tokenize/parsing_tests.rs
Thread observers through link, image, and footnote parsing with updated tests.
Observed wrapping and classification
src/wrap/inline/*, src/wrap/paragraph.rs
Move wrapping orchestration into wrapping, propagate observers through classification and span handling, and remove direct tracing calls.
Benchmark boundary and validation
Cargo.toml, Makefile, benches/*, src/wrap/bench_internals.rs, tests/*
Add feature-gated benchmark shims, Criterion benchmarks, fixture checks, and a corrected wrapping property assertion.
Observer boundary documentation
docs/adrs/*, docs/architecture.md, docs/contents.md, docs/developers-guide.md
Document event ownership, tracing adaptation, performance rules, benchmarks, and event mappings.

Sequence Diagram(s)

sequenceDiagram
  participant wrap_preserving_code_observed
  participant segment_inline_observed
  participant InlineFragment
  participant TracingObserver
  wrap_preserving_code_observed->>segment_inline_observed: tokenize text with observer
  segment_inline_observed->>TracingObserver: emit parsing events
  wrap_preserving_code_observed->>InlineFragment: classify grouped spans
  InlineFragment->>TracingObserver: emit classification events
Loading

Possibly related issues

  • leynos/mdtablefix#306 — Reworks the same find_footnote_end tracing path and its tests.

Possibly related PRs

Suggested labels: Issue

Suggested reviewers: leynos

Poem

Tokens march in lines of light,
Observers catch each parsed insight.
Footnotes couple, fragments flow,
Tracing tells what parsers know.
Benchmarks watch the wrapper go.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 2 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Bench-fixture tests only sample index 0, and the claimed no-subscriber predicate case never uses a real TracingObserver path. Assert every generated fixture item, and add a non-traced test that calls the observer-backed path with TracingObserver but no subscriber.
Security And Privacy ❌ Error TracingObserver still logs a bounded raw token snippet for FragmentClassified, exposing user content through traces and docs. Replace token snippets with metadata-only fields such as token_length, kind, and truncated; remove snippet claims from docs and tests.
Testing (Compile-Time / Ui) ⚠️ Warning New compile-time surface (bench-internals feature/module) lacks any trybuild/UI fixture; existing compile-pass tests do not cover it. Add a trybuild pass fixture for the feature-gated bench internals (and any needed fail case) to exercise the new compile-time surface.
Observability ⚠️ Warning TracingObserver still logs fragment classified with token = %snippet, exposing arbitrary Markdown payloads and high-cardinality content. Replace snippet logging with metadata only (e.g. token_length, kind, truncated) and keep all derived work inside the adapter.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the PR’s main change and includes the linked issue reference (#309).
Description check ✅ Passed The description directly matches the refactor, documentation, and benchmark changes in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
User-Facing Documentation ✅ Passed The PR only adds internal observer/benchmark plumbing; docs/users-guide.md already covers the user-visible wrap behaviour, so no new user-facing docs were required.
Developer Documentation ✅ Passed Docs are in sync: the developer guide, architecture, ADR 0006, and contents index all document the observer boundary, with no locale mirrors or stale related roadmap items found.
Module-Level Documentation ✅ Passed Accept the PR: the full Rust tree shows 0 modules missing leading //! docs, and the new wrap/bench modules explain their role and boundaries.
Testing (Unit And Behavioural) ✅ Passed PASS: The PR adds unit/proptest coverage for the new observer and span/fragment invariants, plus integration tests over the public wrap_text boundary; no end-to-end flow is exposed here.
Testing (Property / Proof) ✅ Passed Substantive proptest coverage was added for the new invariants: UTF-8-safe snippet truncation, date matching, normalisation idempotence, and parsing predicates.
Unit Architecture ✅ Passed PASS: query helpers take explicit ObserverHandle seams, emit borrowed Events only, and tracing/derived work stay isolated in TracingObserver and bench shims.
Domain Architecture ✅ Passed Inline/tokenize domain code now uses ObserverHandle and Event only; direct tracing imports are confined to TracingObserver.
Performance And Resource Use ✅ Passed PASS: New loops are bounded, observer payloads are borrowed and gated, and benchmarks cover realistic document and inline sizes.
Concurrency And State ✅ Passed State stays stack-bound via Option<&mut dyn Observer>; TracingObserver is stateless and no locks, atomics, async, or shared globals were added.
Architectural Complexity And Maintainability ✅ Passed The Observer boundary isolates real tracing coupling, keeps construction explicit, avoids cycles, and documents ownership and reuse in ADR/docs.
Rust Compiler Lint Integrity ✅ Passed PASS: No new lint suppressions or artificial anchors were added; feature-gated bench/test helpers are referenced where compiled, and remaining clones look ownership-preserving.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-309-isolate-vendor-specific-tracing-from-domain-logic-behind-adapter-boundaries

Comment @coderabbitai help to get the list of available commands.

@leynos
leynos marked this pull request as ready for review July 26, 2026 02:22

@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: d403b2519b

ℹ️ 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".

Comment on lines +32 to +35
if let Some(observer) = observer.as_deref_mut() {
observer.observe(Event::FootnoteReferenceParsed {
token_length: text[start..text_end].chars().count(),
});

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 Gate diagnostic field computation before constructing events

When wrapping runs without a DEBUG subscriber, ParagraphWriter still supplies Some(TracingObserver), so this block performs chars().count() for every parsed footnote; links do the same at lines 50-53, and fragment snippets are also built before the adapter checks the active level. These computations were previously inside tracing::enabled! guards, so long inline tokens now incur unnecessary full scans even when no event is emitted. Gate observer creation at the wrapping boundary or defer field computation until after the level check, as required by docs/developers-guide.md:608-612.

AGENTS.md reference: AGENTS.md:L38-L43

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@coderabbitai Has this now been resolved in the latest commit?

Use codegraph analysis to determine your answer.

If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Current PR head =='
git rev-parse HEAD
git status --short

printf '%s\n' '== Relevant module outlines =='
ast-grep outline src/wrap/paragraph.rs --items all
ast-grep outline src/wrap/tracing_adapter.rs --items all
ast-grep outline src/wrap/observer.rs --items all
ast-grep outline src/wrap/tokenize/parsing.rs --items all

printf '%s\n' '== Observer construction and observed wrapping call sites =='
rg -n -C 4 --type rust \
  'TracingObserver|wrap_preserving_code_observed|segment_inline_observed|parse_link_or_image\s*\(' \
  src

printf '%s\n' '== Event variants, payload construction, and adapter level guards =='
rg -n -C 5 --type rust \
  'FootnoteReferenceParsed|LinkOrImageParsed|FragmentClassified|FootnoteLabelRecognized|enabled!\s*\(|Level::' \
  src/wrap

printf '%s\n' '== Diagnostic-gating guidance =='
rg -n -C 5 '608|tracing::enabled|enabled!' docs/developers-guide.md AGENTS.md 2>/dev/null || true

printf '%s\n' '== Focused source excerpts =='
for f in src/wrap/paragraph.rs src/wrap/tracing_adapter.rs src/wrap/observer.rs src/wrap/inline/fragment.rs; do
  if [ -f "$f" ]; then
    echo "--- $f"
    sed -n '1,260p' "$f"
  fi
done

Length of output: 66608


@leynos, this is resolved in the latest commit.

Trace the dataflow: ParagraphWriter still installs TracingObserver, but parse_link_or_image now emits only borrowed token slices. TracingObserver performs chars().count() and fragment snippet construction only inside tracing::enabled!-guarded match arms. This matches the documented performance discipline in docs/developers-guide.md.

I cannot resolve this thread via the API because it was authored by chatgpt-codex-connector[bot], not coderabbitai; GitHub resolution is restricted to this reviewer’s own comments.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@coderabbitai Has this now been resolved in the latest commit?

Use codegraph analysis to determine your answer.

If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR head =='
git rev-parse HEAD

echo '== Code graph: observer construction, event production, and tracing emission =='
ast-grep outline src/wrap --items all --type function,struct,enum,trait --match 'TracingObserver|Observer|parse_link_or_image|find_footnote_end|new_observed|wrap_preserving_code_observed' || true

echo '== Relevant call sites and costly diagnostic expressions =='
rg -n -C 5 --type rust \
  'TracingObserver|FootnoteReferenceParsed|LinkOrImageParsed|FragmentClassified|chars\(\)\.count\(\)|snippet|tracing::enabled!\s*\(|Level::' \
  src/wrap

echo '== Focused current implementations =='
for file in src/wrap/observer.rs src/wrap/tracing_adapter.rs src/wrap/paragraph.rs src/wrap/inline/fragment.rs src/wrap/tokenize/parsing.rs; do
  echo "--- ${file}"
  sed -n '1,280p' "$file"
done

echo '== Review thread metadata for the supplied comment =='
gh api graphql -F owner='leynos' -F name='mdtablefix' -F number=412 -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          comments(first: 20) {
            nodes {
              id
              databaseId
              author { login }
              body
              url
            }
          }
        }
      }
    }
  }
}' | jq -r '
  .data.repository.pullRequest.reviewThreads.nodes[]
  | select(any(.comments.nodes[]; .url == "https://github.com/leynos/mdtablefix/pull/412#discussion_r3651562616"))
  | {id, isResolved, comments: [.comments.nodes[] | {author: .author.login, url, body}]}
'

Length of output: 71433


@leynos, treat this finding as resolved at 695c804ff319738c0126f8e7652d76adf111248f.

The current dataflow defers expensive diagnostics correctly:

  • src/wrap/tokenize/parsing.rs emits borrowed token slices rather than calling chars().count().
  • src/wrap/inline/fragment.rs emits borrowed fragment text rather than constructing a snippet.
  • src/wrap/tracing_adapter.rs performs token.chars().count() and trace_text_snippet(token) only inside tracing::enabled!-guarded match arms.

Do not require further code changes for this comment. I cannot resolve the GitHub thread through the API because its original review comment belongs to chatgpt-codex-connector, not coderabbitai; resolve it from the PR UI or as that reviewer.

Comment thread src/wrap/inline/span_helpers.rs
Comment thread src/wrap/observer.rs
Comment thread src/wrap/inline.rs Outdated
Comment on lines +344 to +348
pub(super) fn wrap_preserving_code_observed(
text: &str,
width: usize,
observer: &mut Option<&mut dyn crate::wrap::observer::Observer>,
) -> Vec<String> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Split inline.rs back below the 400-line cap

Adding the observed wrapper entry point expands src/wrap/inline.rs to 405 lines, exceeding the repository's hard 400-line limit. Move the observer-facing orchestration or another coherent unit into a focused module rather than leaving this file over the documented maintainability threshold.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@coderabbitai Has this now been resolved in the latest commit?

Use codegraph analysis to determine your answer.

If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Checked-out PR state =="
git rev-parse HEAD
git status --short
printf '\n== src/wrap/inline.rs line count ==\n'
wc -l src/wrap/inline.rs

printf '\n== Codegraph outline: inline module ==\n'
ast-grep outline src/wrap/inline.rs --items all --view expanded

printf '\n== Extracted wrapping module presence and outline ==\n'
if [ -f src/wrap/inline/wrapping.rs ]; then
  wc -l src/wrap/inline/wrapping.rs
  ast-grep outline src/wrap/inline/wrapping.rs --items all --view expanded
else
  echo "src/wrap/inline/wrapping.rs is absent"
fi

printf '\n== Relevant module declarations and re-exports ==\n'
rg -n -C 2 'mod wrapping|wrapping::wrap_preserving_code|wrap_preserving_code_observed' src/wrap/inline.rs

printf '\n== PR head and matching review threads ==\n'
gh pr view 412 --repo leynos/mdtablefix --json headRefOid,url \
  --jq '{url, headRefOid}'
gh api graphql -F owner='leynos' -F name='mdtablefix' -F number=412 -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          path
          line
          originalLine
          comments(first: 20) {
            nodes {
              author { login }
              body
              url
            }
          }
        }
      }
    }
  }
}' | jq -r '
  .data.repository.pullRequest.reviewThreads.nodes[]
  | select(.path == "src/wrap/inline.rs")
  | select([.comments.nodes[].body] | join("\n") | contains("Split inline.rs back below the 400-line cap"))
  | {id, isResolved, path, line, originalLine, comments: [.comments.nodes[] | {author: .author.login, url, body}]}
'

Length of output: 10885


@leynos, the finding is resolved in 9d6de44b72d2dc17e286a9b3f9d0dadf964b5a01.

src/wrap/inline.rs is now 48 lines. The observer-facing wrapping orchestration was coherently extracted into src/wrap/inline/wrapping.rs (388 lines), while inline.rs retains module declarations and focused re-exports.

I cannot resolve this specific GitHub thread through the API because its original comment author is chatgpt-codex-connector, not coderabbitai; resolve it from the PR UI or through that reviewer’s integration.


🧬 Code Graph Analysis Results

File: src/wrap/inline.rs

No additional relevant snippets beyond module/predicate re-exports and public wrapping entry points.


File: src/wrap/inline/predicates.rs

Lines 14-16

pub(in crate::wrap::inline) fn is_opening_punct(c: char) -> bool {
    matches!(c, '(' | '[' | '"' ) || "“‘([【《「『".contains(c)
}

Lines 18-24

pub(in crate::wrap::inline) fn is_trailing_punct(c: char) -> bool {
    // ASCII closers + common Unicode closers and word-final punctuation
    matches!(
        c,
        '.' | ',' | ';' | ':' | '!' | '?' | ')' | ']' | '"' | '\''
    ) || "…—–»›)]】》」』、。,:;!?”.’".contains(c)
}

Lines 109-121

pub(in crate::wrap::inline) fn looks_like_footnote_ref(
    token: &str,
    observer: &mut ObserverHandle<'_>,
) -> bool {
    let result = token
        .strip_prefix("[^")
        .and_then(|label| label.strip_suffix(']'))
        .is_some_and(|label| !label.is_empty());
    if let Some(observer) = observer.as_deref_mut() {
        observer.observe(Event::FootnoteRefChecked { token, result });
    }
    result
}

Lines 124-133

pub(in crate::wrap::inline) fn ends_with_footnote_ref(
    token: &str,
    observer: &mut ObserverHandle<'_>,
) -> bool {
    let Some(start) = token.rfind("[^") else {
        return false;
    };

    looks_like_footnote_ref(&token[start..], observer)
}

Lines 136-138

pub(in crate::wrap::inline) fn is_whitespace_token(token: &str) -> bool {
    token.chars().all(char::is_whitespace)
}

Lines 141-143

pub(in crate::wrap::inline) fn is_inline_code_token(token: &str) -> bool {
    token.len() > 1 && token.starts_with('`') && token.ends_with('`')
}

Lines 190-205

pub(in crate::wrap::inline) fn fragment_is_link(text: &str) -> bool {
    if looks_like_link(text) {
        return true;
    }
    let mut candidate = link_text_after_leading_openers(text);
    while !candidate.is_empty() {
        if looks_like_link(candidate) {
            return true;
        }
        let Some(next) = strip_outer_link_wrapper_suffix(candidate) else {
            break;
        };
        candidate = next;
    }
    false
}

File: src/wrap/inline/wrapping.rs

Lines 119-121

pub(in crate::wrap) fn determine_token_span(tokens: &[String], start: usize) -> (usize, usize) {
    determine_token_span_observed(tokens, start, &mut None)
}

(Wrap-related helper; calls determine_token_span_observed(tokens, start, ...) and returns (start_idx, end_idx).)

Lines 325-328

pub(in crate::wrap) fn wrap_preserving_code(text: &str, width: usize) -> Vec<String> {
    let mut observer = crate::wrap::observer::NoOpObserver;
    wrap_preserving_code_observed(text, width, &mut Some(&mut observer))
}

Lines 330-388

pub(in crate::wrap) fn wrap_preserving_code_observed(
    text: &str,
    width: usize,
    observer: &mut ObserverHandle<'_>,
) -> Vec<String> {
    let tokens = tokenize::segment_inline_observed(text, observer);
    if tokens.is_empty() {
        return Vec::new();
    }

    let tokens = normalize_footnote_ref_spacing(&tokens, observer);
    let fragments = build_fragments(&tokens, observer);
    let mut lines = Vec::new();
    let mut buffer: Vec<InlineFragment> = Vec::new();

    for fragment in fragments {
        if should_flush_boundary_link(&lines, &buffer, &fragment, width) {
            lines.push(render_line(&buffer, false, !lines.is_empty()));
            buffer.clear();
            if fragment.is_whitespace() {
                continue;
            }
        }

        buffer.push(fragment);
        let wrapped = wrap_first_fit(&buffer, &[width_as_f64(width)]);
        let raw_lines = wrapped.iter().map(|line| line.to_vec()).collect::<Vec<_>>();
        let mut grouped_lines = merge_whitespace_only_lines(&raw_lines, width);
        rebalance_atomic_tails(&mut grouped_lines, width);

        if grouped_lines.len() == 1 {
            continue;
        }

        if let Some((link_line, remaining_line)) = grouped_lines
            .get(grouped_lines.len() - 2)
            .zip(grouped_lines.last())
            .and_then(|(previous, line)| split_boundary_link_line(previous, line, width))
        {
            for line in &grouped_lines[..grouped_lines.len() - 1] {
                lines.push(render_line(line, false, !lines.is_empty()));
            }
            lines.push(render_line(&link_line, false, !lines.is_empty()));
            buffer = remaining_line;
            continue;
        }

        for line in &grouped_lines[..grouped_lines.len() - 1] {
            lines.push(render_line(line, false, !lines.is_empty()));
        }
        buffer = grouped_lines.pop().unwrap_or_default();
    }

    if !buffer.is_empty() {
        lines.push(render_line(&buffer, true, !lines.is_empty()));
    }

    lines
}

(Main observer-aware inline wrapping pipeline: tokenizes with segment_inline_observed, normalizes footnote spacing, builds fragments, wraps by first-fit, groups/merges whitespace-only lines, rebalances atomic tails, handles boundary links via should_flush_boundary_link and split_boundary_link_line, and renders final Vec<String> lines.)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@coderabbitai Has this now been resolved in the latest commit?

Use codegraph analysis to determine your answer.

If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="leynos/mdtablefix"
needle="Split inline.rs back below the 400-line cap"

echo "== PR head and checked-out commit =="
gh pr view 412 --repo "$repo" --json headRefOid,url --jq '{url, headRefOid}'
git rev-parse HEAD

echo
echo "== Relevant file sizes =="
wc -l src/wrap/inline.rs
if [ -f src/wrap/inline/wrapping.rs ]; then
  wc -l src/wrap/inline/wrapping.rs
fi

echo
echo "== Codegraph: facade module =="
ast-grep outline src/wrap/inline.rs --items all --view expanded

echo
echo "== Codegraph: extracted wrapping orchestration =="
if [ -f src/wrap/inline/wrapping.rs ]; then
  ast-grep outline src/wrap/inline/wrapping.rs --items all --view expanded
else
  echo "src/wrap/inline/wrapping.rs is absent"
fi

echo
echo "== Facade-to-extracted-module links =="
rg -n -C 2 'mod wrapping|wrapping::wrap_preserving_code|wrap_preserving_code_observed' \
  src/wrap/inline.rs src/wrap/inline/wrapping.rs

echo
echo "== Matching review thread =="
gh api graphql -F owner='leynos' -F name='mdtablefix' -F number=412 -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          path
          line
          originalLine
          comments(first: 30) {
            nodes {
              author { login }
              body
              url
            }
          }
        }
      }
    }
  }
}' | jq --arg needle "$needle" '
  .data.repository.pullRequest.reviewThreads.nodes[]
  | select(.path == "src/wrap/inline.rs")
  | select(any(.comments.nodes[]; .body | contains($needle)))
  | {
      id, isResolved, path, line, originalLine,
      comments: [.comments.nodes[] | {author: .author.login, url, body}]
    }
'

Length of output: 18793


@leynos, treat this finding as resolved in the latest commit (695c804ff319738c0126f8e7652d76adf111248f).

Codegraph analysis confirms that:

  • src/wrap/inline.rs is now 48 lines, below the 400-line cap.
  • The observer-aware orchestration lives coherently in src/wrap/inline/wrapping.rs, which is 388 lines.
  • The facade declares mod wrapping and re-exports wrap_preserving_code_observed, preserving the existing inline module surface.

Resolve thread PRRT_kwDOO7HqGM6Tz9LE through the GitHub PR UI or the chatgpt-codex-connector integration. I cannot resolve it through the API because that integration authored the original review comment.


🧬 Code Graph Analysis Results

src/wrap/inline/predicates.rs

Lines 14-16

pub(in crate::wrap::inline) fn is_opening_punct(c: char) -> bool {
    matches!(c, '(' | '[' | '"') || "“‘([【《「『".contains(c)
}

Lines 18-24

pub(in crate::wrap::inline) fn is_trailing_punct(c: char) -> bool {
    // ASCII closers + common Unicode closers and word-final punctuation
    matches!(
        c,
        '.' | ',' | ';' | ':' | '!' | '?' | ')' | ']' | '"' | '\''
    ) || "…—–»›)]】》」』、。,:;!?”.’".contains(c)
}

Lines 109-121

pub(in crate::wrap::inline) fn looks_like_footnote_ref(
    token: &str,
    observer: &mut ObserverHandle<'_>,
) -> bool {
    let result = token
        .strip_prefix("[^")
        .and_then(|label| label.strip_suffix(']'))
        .is_some_and(|label| !label.is_empty());
    if let Some(observer) = observer.as_deref_mut() {
        observer.observe(Event::FootnoteRefChecked { token, result });
    }
    result
}

Lines 124-133

pub(in crate::wrap::inline) fn ends_with_footnote_ref(
    token: &str,
    observer: &mut ObserverHandle<'_>,
) -> bool {
    let Some(start) = token.rfind("[^") else {
        return false;
    };

    looks_like_footnote_ref(&token[start..], observer)
}

Lines 136-138

pub(in crate::wrap::inline) fn is_whitespace_token(token: &str) -> bool {
    token.chars().all(char::is_whitespace)
}

Lines 141-143

pub(in crate::wrap::inline) fn is_inline_code_token(token: &str) -> bool {
    token.len() > 1 && token.starts_with('`') && token.ends_with('`')
}

Lines 190-205

pub(in crate::wrap::inline) fn fragment_is_link(text: &str) -> bool {
    if looks_like_link(text) {
        return true;
    }
    let mut candidate = link_text_after_leading_openers(text);
    while !candidate.is_empty() {
        if looks_like_link(candidate) {
            return true;
        }
        let Some(next) = strip_outer_link_wrapper_suffix(candidate) else {
            break;
        };
        candidate = next;
    }
    false
}

src/wrap/inline/wrapping.rs

Lines 119-121

pub(in crate::wrap) fn determine_token_span(tokens: &[String], start: usize) -> (usize, usize) {
    determine_token_span_observed(tokens, start, &mut None)
}

Lines 325-328

pub(in crate::wrap) fn wrap_preserving_code(text: &str, width: usize) -> Vec<String> {
    let mut observer = crate::wrap::observer::NoOpObserver;
    wrap_preserving_code_observed(text, width, &mut Some(&mut observer))
}

Lines 330-388 (summary)

/// fn wrap_preserving_code_observed(text: &str, width: usize, observer: &mut ObserverHandle<'_>) -> Vec<String>
/// - Tokenizes input with `tokenize::segment_inline_observed(text, observer)`.
/// - Returns `Vec::new()` if tokenization yields no tokens.
/// - Applies footnote-ref spacing normalization: `normalize_footnote_ref_spacing(&tokens, observer)`.
/// - Builds inline fragments via `build_fragments(&tokens, observer)`.
/// - Iteratively fits fragments into lines using `wrap_first_fit(&buffer, &[width_as_f64(width)])`.
/// - Merges/adjusts line groupings:
///   - `merge_whitespace_only_lines(&raw_lines, width)`
///   - `rebalance_atomic_tails(&mut grouped_lines, width)`
/// - Handles special cases for boundary links using:
///   - `should_flush_boundary_link(...)`
///   - `split_boundary_link_line(previous, line, width)`
/// - Renders finished lines using `render_line(..., keep_inline_code=true/false?, is_not_first_line?)`.
/// - Returns accumulated `lines`, pushing the last buffered line if non-empty.
/// - No explicit error handling; control flow is driven by fragment boundaries and line grouping results.

@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the Issue label Jul 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/wrap/inline/span_helpers.rs (1)

67-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Three helper functions can never report to an active observer. date_token_span, should_couple_whitespace, and matches_footnote_ref_spacing all call footnote-ref predicates with a hardcoded &mut None because none of them accept an observer parameter — even though each is invoked from a context (determine_token_span_observed, wrap_preserving_code_observed) that holds a live observer. The single root cause is these three signatures not threading the caller's observer through.

  • src/wrap/inline/span_helpers.rs#L67-L86: add an observer: &mut Option<&mut dyn Observer> parameter to date_token_span and forward it into try_couple_footnote_reference instead of &mut None.
  • src/wrap/inline/span_helpers.rs#L141-L162: add an observer parameter to should_couple_whitespace and forward it into looks_like_footnote_ref instead of &mut None.
  • src/wrap/inline/normalize.rs#L43-L50: add an observer parameter to matches_footnote_ref_spacing (and its caller normalize_footnote_ref_spacing) and forward it instead of &mut None.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wrap/inline/span_helpers.rs` around lines 67 - 86, Thread the live
observer through all three helper paths: in
src/wrap/inline/span_helpers.rs:67-86, add an observer parameter to
date_token_span and pass it to try_couple_footnote_reference; in
src/wrap/inline/span_helpers.rs:141-162, add the parameter to
should_couple_whitespace and pass it to looks_like_footnote_ref; in
src/wrap/inline/normalize.rs:43-50, add it to matches_footnote_ref_spacing and
normalize_footnote_ref_spacing, forwarding it instead of &mut None. Update all
callers, including determine_token_span_observed and
wrap_preserving_code_observed.
src/wrap/inline.rs (1)

338-402: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split src/wrap/inline.rs into a submodule.
Keep the observer-aware wrapping helpers (determine_token_span_observed, build_fragments, wrap_preserving_code_observed) out of this file so src/wrap/inline.rs stays within the 400-line cap; it is already at 405 lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wrap/inline.rs` around lines 338 - 402, Split the observer-aware wrapping
helpers determine_token_span_observed, build_fragments, and
wrap_preserving_code_observed into a dedicated inline submodule, updating
visibility and imports so existing callers and tests continue to work. Keep
inline.rs focused on the remaining public wrapping API and under the 400-line
limit.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/wrap/inline.rs`:
- Line 258: Import FragmentKind from crate::wrap::observer in the relevant
scope, then update both call sites around the line-length checks to use
FragmentKind::Link instead of the fully qualified path.
- Around line 60-64: Introduce a shared pub(crate) ObserverHandle<'a> alias in
observer.rs for the optional mutable Observer reference, then update
initial_token_span in src/wrap/inline.rs#L60-L64, new_observed in
src/wrap/inline/fragment.rs#L51-L51, looks_like_footnote_ref in
src/wrap/inline/predicates.rs#L109-L111, and try_couple_footnote_reference in
src/wrap/inline/span_helpers.rs#L223-L223 to accept &mut ObserverHandle<'_>,
adding the necessary imports and replacing the repeated verbose types.

In `@src/wrap/observer.rs`:
- Around line 51-56: Make NoOpObserver test-only by adding #[cfg(test)] to the
struct and removing its dead_code allowance, while preserving its existing
documentation and visibility.

In `@src/wrap/paragraph.rs`:
- Around line 167-171: Extract the shared TracingObserver setup and
wrap_preserving_code_observed call from wrap_with_prefix and
append_wrapped_with_prefix_width into a small private helper, then update both
call sites to use it while preserving their existing text and width arguments.

---

Outside diff comments:
In `@src/wrap/inline.rs`:
- Around line 338-402: Split the observer-aware wrapping helpers
determine_token_span_observed, build_fragments, and
wrap_preserving_code_observed into a dedicated inline submodule, updating
visibility and imports so existing callers and tests continue to work. Keep
inline.rs focused on the remaining public wrapping API and under the 400-line
limit.

In `@src/wrap/inline/span_helpers.rs`:
- Around line 67-86: Thread the live observer through all three helper paths: in
src/wrap/inline/span_helpers.rs:67-86, add an observer parameter to
date_token_span and pass it to try_couple_footnote_reference; in
src/wrap/inline/span_helpers.rs:141-162, add the parameter to
should_couple_whitespace and pass it to looks_like_footnote_ref; in
src/wrap/inline/normalize.rs:43-50, add it to matches_footnote_ref_spacing and
normalize_footnote_ref_spacing, forwarding it instead of &mut None. Update all
callers, including determine_token_span_observed and
wrap_preserving_code_observed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aaafb396-6a69-4f4c-b664-83026f14d8ca

📥 Commits

Reviewing files that changed from the base of the PR and between 4fea5c0 and d403b25.

📒 Files selected for processing (18)
  • src/wrap.rs
  • src/wrap/inline.rs
  • src/wrap/inline/fragment.rs
  • src/wrap/inline/normalize.rs
  • src/wrap/inline/postprocess.rs
  • src/wrap/inline/postprocess_tests.rs
  • src/wrap/inline/predicate_tracing_tests.rs
  • src/wrap/inline/predicates.rs
  • src/wrap/inline/span_helper_tracing_tests.rs
  • src/wrap/inline/span_helpers.rs
  • src/wrap/inline/tests.rs
  • src/wrap/inline/tracing_events.rs
  • src/wrap/observer.rs
  • src/wrap/paragraph.rs
  • src/wrap/tokenize/mod.rs
  • src/wrap/tokenize/parsing.rs
  • src/wrap/tokenize/parsing_tests.rs
  • src/wrap/tracing_adapter.rs
💤 Files with no reviewable changes (3)
  • src/wrap/inline/predicate_tracing_tests.rs
  • src/wrap/inline/span_helper_tracing_tests.rs
  • src/wrap/inline/tracing_events.rs

Comment thread src/wrap/inline.rs Outdated
Comment thread src/wrap/inline.rs Outdated
Comment thread src/wrap/observer.rs
Comment thread src/wrap/paragraph.rs Outdated
@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

base="$(git merge-base HEAD origin/main 2>/dev/null || git rev-parse HEAD~1)"

printf '%s\n' '=== Changed files ==='
git diff --name-status "$base" HEAD

printf '%s\n' '=== Recent commits ==='
git log --oneline -12

printf '%s\n' '=== Observer, adapter, predicate test evidence ==='
for f in src/wrap/observer.rs src/wrap/tracing_adapter.rs src/wrap/inline/predicates.rs; do
  if [ -f "$f" ]; then
    printf '\n--- %s outline ---\n' "$f"
    ast-grep outline "$f" --items all || true
    printf '\n--- %s relevant lines ---\n' "$f"
    rg -n -C 3 \
      -e 'NoOpObserver' \
      -e 'allow\(dead_code\)' \
      -e 'FootnoteRefChecked' \
      -e 'footnote reference checked' \
      -e 'looks_like_footnote_ref' \
      -e 'ends_with_footnote_ref' \
      -e 'enabled!' \
      "$f" || true
  fi
done

printf '%s\n' '=== Benchmark registration and sources ==='
rg -n -i -C 3 \
  -e '\[\[bench\]\]' \
  -e 'criterion|divan|iai' \
  -e 'bench' \
  Cargo.toml Makefile docs README.md 2>/dev/null || true
fd -t f . -E .git -E target | rg -i '(^|/)(benches|benchmarks)/|bench.*\.(rs|md)$' || true

printf '%s\n' '=== Documentation evidence ==='
for f in docs/developers-guide.md docs/architecture.md docs/contents.md docs/adrs/0006-observer-boundary-for-tracing.md; do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    rg -n -i -C 2 \
      -e 'observer|tracing|benchmark|performance|disabled' \
      "$f" || true
  fi
done

Length of output: 29107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== Benchmark implementation outline ==='
ast-grep outline benches/wrap_observer.rs --items all
ast-grep outline src/wrap/bench_internals.rs --items all
ast-grep outline tests/bench_fixtures.rs --items all

printf '%s\n' '=== Benchmark implementation ==='
cat -n benches/wrap_observer.rs
printf '%s\n' '=== Benchmark internals ==='
cat -n src/wrap/bench_internals.rs
printf '%s\n' '=== Fixture tests ==='
cat -n tests/bench_fixtures.rs

printf '%s\n' '=== Makefile validation targets ==='
sed -n '1,90p' Makefile

printf '%s\n' '=== Bench feature/module wiring ==='
rg -n -C 3 \
  -e 'bench-internals' \
  -e 'bench_internals' \
  -e 'wrap_text_realistic_document' \
  -e 'observer_none' \
  -e 'tracing_observer_disabled' \
  src benches tests Cargo.toml Makefile

Length of output: 19199


@leynos Mark all four checks resolved by static inspection. No further implementation work or AI-agent prompt is required.

Check Status Evidence
Testing (Overall) Resolved src/wrap/inline/predicates.rs now contains traced positive and negative looks_like_footnote_ref tests and an ends_with_footnote_ref test. They assert the footnote reference checked event plus token_length and result fields.
Rust Compiler Lint Integrity Resolved src/wrap/observer.rs gates NoOpObserver and its Observer implementation with #[cfg(test)]. The #[allow(dead_code)] suppression is absent.
Developer Documentation Resolved docs/developers-guide.md, docs/architecture.md, and docs/contents.md document the observer boundary. ADR 0006 records ownership, adapter rules, tracing gates, and the performance invariant.
Performance And Resource Use Resolved TracingObserver calculates Unicode lengths and snippets only after tracing::enabled! succeeds. Criterion benchmark benches/wrap_observer.rs is registered in Cargo.toml and compares observer_none with tracing_observer_disabled, alongside the public wrap_text path over a deterministic large Markdown document. Fixture tests prove links, inline code, and footnote references are exercised and preserved.

Run the full validation suite before merging; this sandbox can inspect source but cannot execute repository code:

make check-fmt
make lint
make typecheck
make test
make markdownlint
make nixie
RUSTFLAGS="-D warnings" make bench

Treat any warning or failure from these commands as blocking.

leynos and others added 4 commits July 27, 2026 20:04
Move inline classification and parsing observations into domain events.\n\nKeep tracing integration at the paragraph-facing boundary so domain code\ncan be called without a subscriber or vendor-specific guards.
Refine the tracing-isolation work so the inline-wrapping domain stays
free of vendor-specific tracing and the observer port is documented.

- Add a `pub(crate) ObserverHandle<'a>` alias and replace the repeated
  `&mut Option<&mut dyn Observer>` signatures across the wrap modules.
- Gate `NoOpObserver` behind `#[cfg(test)]` and drop its `dead_code`
  allowance; only test paths construct it.
- Move the observer-aware orchestration (span grouping, fragment
  building, line fitting) into `wrap/inline/wrapping.rs`, keeping
  `inline.rs` on the public surface and both files under the 400-line
  limit; import `FragmentKind` rather than fully qualifying it.
- Thread the live observer through `date_token_span`,
  `should_couple_whitespace`, and footnote-ref normalization instead of
  discarding it via `&mut None`.
- Extract the shared `TracingObserver` setup in `paragraph.rs` into a
  `wrap_observed` helper.
- Make `Event` variants carry borrowed `&str` slices and move all
  derived work (`chars().count()`, snippet truncation) into
  `TracingObserver` behind its level gate, so a disabled subscriber pays
  only a branch on the hot path.
- Restore date diagnostics through a new `DateSequenceMatched` event
  emitted from `date_token_span`.
- Add traced tests for `FootnoteRefChecked` and `DateSequenceMatched`;
  tighten the checklist-span proptest closing-fence assertion so it no
  longer collides with the opening fence.
- Document the observer boundary: developers-guide observability section
  and event/log mapping, new ADR 0006, and architecture notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prove that the observer/tracing adapter adds no derived-payload work on
the inline wrapping hot path when tracing is disabled, and establish a
baseline for large realistic wrapping workloads.

- Add Criterion (minimal features) as a dev-dependency and register a
  `wrap_observer` bench target gated on a new `bench-internals` feature.
- Add `src/wrap/bench_internals.rs`: feature-gated, `#[doc(hidden)]`
  shims exposing the crate-internal `wrap_preserving_code_observed` for
  the observer-`None` and `TracingObserver`-disabled paths, plus
  deterministic large fixtures mixing prose, links, inline code, and
  footnote references. Never compiled into production builds.
- Benchmark the public `wrap_text` document path and the inline path
  with the observer disabled (`None`) versus `TracingObserver` with no
  subscriber installed; the latter two must match.
- Add `tests/bench_fixtures.rs` proving the fixtures exercise links,
  code spans, and footnote references and that wrapping preserves them
  (content assertions only, no timing).
- Add a `make bench` target and a developer-guide section documenting
  the command and the disabled-tracing performance invariant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile the observer/tracing work with main's restructuring of the
paragraph module (now split into hard_break, pending, spanning_code, and
tail_reflow submodules) after rebasing onto origin/main.

- Make `wrap_preserving_code` the production inline entry point again,
  wiring up a `TracingObserver` internally, so main's new call sites in
  `paragraph/spanning_code.rs` and `paragraph/tail_reflow.rs` route
  through the observer boundary. This replaces the branch's test-only
  `wrap_preserving_code` and the redundant `paragraph::wrap_observed`
  helper, which is removed.
- Gate the `wrap_preserving_code_observed` re-export behind the
  `bench-internals` feature, its only remaining external caller.
- Rebuild Cargo.lock from main's, re-adding the criterion dev-dependency.
- Repair an architecture.md merge artifact that mangled the footnote
  before/after example, and drop stray blank lines flagged by
  markdownlint in the merged docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lodyai
lodyai Bot force-pushed the issue-309-isolate-vendor-specific-tracing-from-domain-logic-behind-adapter-boundaries branch from 695c804 to 84b2a9c Compare July 27, 2026 18:18
@buzzybee-df12

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/developers-guide.md`:
- Around line 722-727: Update the benchmark documentation in the comparison of
observer_none and tracing_observer_disabled to state that disabled tracing still
incurs dynamic dispatch, event matching, and a per-event tracing::enabled!
check. Require that no derived-payload work occurs and that overhead remains
bounded, rather than claiming the two cases must have equal timings.

In `@Makefile`:
- Around line 22-24: Update the Makefile bench target’s cargo invocation to
propagate the existing RUSTFLAGS with -D warnings, ensuring benchmark
compilation fails on warnings while preserving the current features and
wrap_observer benchmark selection.

In `@src/wrap/inline/fragment.rs`:
- Around line 167-169: Pass &mut None to all three looks_like_footnote_ref and
ends_with_footnote_ref probes in src/wrap/inline/fragment.rs lines 167-169,
preserving FragmentClassified as the sole event for the fragment. Also pass &mut
None to both looks_like_footnote_ref calls in src/wrap/inline/normalize.rs lines
50-54 so speculative scans do not emit duplicate observer events.

In `@src/wrap/inline/predicates.rs`:
- Around line 260-264: Rename footnote_ref_check_does_not_require_subscriber to
reflect that it only checks the no-observer path. Add a separate test for the
adapter-without-subscriber scenario by constructing Some(TracingObserver)
outside #[traced_test], then assert looks_like_footnote_ref handles both
footnote and plain inputs without a subscriber.

In `@src/wrap/inline/wrapping.rs`:
- Around line 338-342: Add Rustdoc (`///`) directly above
`wrap_preserving_code_observed` documenting that events are emitted only when
the observer handle is `Some`, and that the adapter is responsible for derived
work. Match the contract detail and style of the sibling `wrap_preserving_code`
documentation.

In `@src/wrap/tracing_adapter.rs`:
- Around line 62-67: Stop emitting Markdown token contents from the
Event::FragmentClassified tracing branch: replace the token snippet field with
non-sensitive metadata such as token_length, kind, and truncated, and remove any
now-unused snippet handling. Update
docs/adrs/0006-observer-boundary-for-tracing.md lines 39-46 to document
metadata-only diagnostics, docs/architecture.md lines 625-632 to remove the
bounded-snippet claim, docs/developers-guide.md lines 678-685 to replace the
raw-token example, and docs/developers-guide.md lines 753-764 to remove the
token field from the event mapping.

In `@tests/bench_fixtures.rs`:
- Around line 31-49: Update
realistic_document_fixture_covers_and_preserves_constructs to validate every
generated construct rather than only index 0. Iterate across all generated
paragraph/clause indices and assert each corresponding code span, link, and
footnote reference is present in both source and wrapped document checks,
preserving the existing failure context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3b447183-25d2-4747-a395-6f637d226f5b

📥 Commits

Reviewing files that changed from the base of the PR and between 4d33369 and 84b2a9c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • Cargo.toml
  • Makefile
  • benches/wrap_observer.rs
  • docs/adrs/0006-observer-boundary-for-tracing.md
  • docs/architecture.md
  • docs/contents.md
  • docs/developers-guide.md
  • src/wrap.rs
  • src/wrap/bench_internals.rs
  • src/wrap/inline.rs
  • src/wrap/inline/fragment.rs
  • src/wrap/inline/normalize.rs
  • src/wrap/inline/postprocess.rs
  • src/wrap/inline/postprocess_tests.rs
  • src/wrap/inline/predicate_tracing_tests.rs
  • src/wrap/inline/predicates.rs
  • src/wrap/inline/span_helper_tracing_tests.rs
  • src/wrap/inline/span_helpers.rs
  • src/wrap/inline/tests.rs
  • src/wrap/inline/tracing_events.rs
  • src/wrap/inline/wrapping.rs
  • src/wrap/observer.rs
  • src/wrap/paragraph.rs
  • src/wrap/tokenize/mod.rs
  • src/wrap/tokenize/parsing.rs
  • src/wrap/tokenize/parsing_tests.rs
  • src/wrap/tracing_adapter.rs
  • tests/bench_fixtures.rs
  • tests/wrap_properties.rs
💤 Files with no reviewable changes (4)
  • src/wrap/inline/predicate_tracing_tests.rs
  • src/wrap/inline/tracing_events.rs
  • src/wrap/inline/span_helper_tracing_tests.rs
  • src/wrap/paragraph.rs

Comment thread docs/developers-guide.md
Comment on lines +722 to +727
The last two cases should measure the same: with tracing disabled, the derived
Unicode length counts and snippet truncation must stay inside `TracingObserver`
behind its `tracing::enabled!` gates, so the adapter adds no per-event work
beyond one branch. A widening gap between `observer_none` and
`tracing_observer_disabled` signals that derived-payload work has leaked back
onto the hot path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Describe the disabled-tracing benchmark overhead accurately.

observer_none avoids Observer::observe; tracing_observer_disabled still performs
dynamic dispatch, event matching, and a tracing::enabled! check per event. Require
no derived-payload work and bounded overhead, rather than equal timings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/developers-guide.md` around lines 722 - 727, Update the benchmark
documentation in the comparison of observer_none and tracing_observer_disabled
to state that disabled tracing still incurs dynamic dispatch, event matching,
and a per-event tracing::enabled! check. Require that no derived-payload work
occurs and that overhead remains bounded, rather than claiming the two cases
must have equal timings.

Comment thread Makefile
Comment on lines +22 to +24
bench: ## Run the observer-boundary wrapping benchmarks
$(CARGO) bench --features bench-internals --bench wrap_observer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make bench fail on warnings.

This target invokes cargo bench without -D warnings, so benchmark-only warnings can pass and the stated warnings-as-errors validation requirement is not enforced. Propagate RUSTFLAGS with -D warnings, or enforce the equivalent in CI.

Proposed fix
 bench: ## Run the observer-boundary wrapping benchmarks
-	$(CARGO) bench --features bench-internals --bench wrap_observer
+	RUSTFLAGS="$(RUSTFLAGS) -D warnings" \
+	$(CARGO) bench --features bench-internals --bench wrap_observer
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 22 - 24, Update the Makefile bench target’s cargo
invocation to propagate the existing RUSTFLAGS with -D warnings, ensuring
benchmark compilation fails on warnings while preserving the current features
and wrap_observer benchmark selection.

Comment on lines +167 to +169
} else if looks_like_footnote_ref(text, observer)
|| looks_like_footnote_ref(trimmed, observer)
|| ends_with_footnote_ref(text, observer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Speculative predicate probes are handed the live observer, so one decision yields several events. looks_like_footnote_ref emits FootnoteRefChecked on every call, and both sites call it repeatedly on variants of the same token merely to reach a verdict. The result is a trace stream where record count depends on short-circuiting and scan order rather than on anything a reader cares about, plus needless dynamic dispatch per token. Observe decisions, not probes.

  • src/wrap/inline/fragment.rs#L167-L169: pass &mut None to the three footnote probes and let the existing FragmentClassified event at Lines 54-59 be the single record for that fragment.
  • src/wrap/inline/normalize.rs#L50-L54: pass &mut None to both looks_like_footnote_ref calls, so the first_match scan and the rewrite loop stop reporting the same window twice.
📍 Affects 2 files
  • src/wrap/inline/fragment.rs#L167-L169 (this comment)
  • src/wrap/inline/normalize.rs#L50-L54
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wrap/inline/fragment.rs` around lines 167 - 169, Pass &mut None to all
three looks_like_footnote_ref and ends_with_footnote_ref probes in
src/wrap/inline/fragment.rs lines 167-169, preserving FragmentClassified as the
sole event for the fragment. Also pass &mut None to both looks_like_footnote_ref
calls in src/wrap/inline/normalize.rs lines 50-54 so speculative scans do not
emit duplicate observer events.

Comment on lines +260 to +264
#[test]
fn footnote_ref_check_does_not_require_subscriber() {
assert!(looks_like_footnote_ref("[^1]", &mut None));
assert!(!looks_like_footnote_ref("plain", &mut None));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

This test does not exercise the "no subscriber" case it claims.

&mut None skips the observer entirely, so nothing ever reaches tracing. The invariant advertised in the module doc — that the predicates behave with the adapter attached but no subscriber installed — needs Some(TracingObserver) outside #[traced_test]. Rename this one to reflect what it checks, and add the adapter-without-subscriber case.

🧪 Proposed test split
     #[test]
-    fn footnote_ref_check_does_not_require_subscriber() {
+    fn footnote_ref_check_works_without_observer() {
         assert!(looks_like_footnote_ref("[^1]", &mut None));
         assert!(!looks_like_footnote_ref("plain", &mut None));
     }
+
+    #[test]
+    fn footnote_ref_check_does_not_require_subscriber() {
+        let mut observer = TracingObserver;
+        assert!(looks_like_footnote_ref("[^1]", &mut Some(&mut observer)));
+        assert!(!looks_like_footnote_ref("plain", &mut Some(&mut observer)));
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wrap/inline/predicates.rs` around lines 260 - 264, Rename
footnote_ref_check_does_not_require_subscriber to reflect that it only checks
the no-observer path. Add a separate test for the adapter-without-subscriber
scenario by constructing Some(TracingObserver) outside #[traced_test], then
assert looks_like_footnote_ref handles both footnote and plain inputs without a
subscriber.

Comment on lines +338 to +342
pub(in crate::wrap) fn wrap_preserving_code_observed(
text: &str,
width: usize,
observer: &mut ObserverHandle<'_>,
) -> Vec<String> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document wrap_preserving_code_observed.

Its sibling wrap_preserving_code carries a full contract description, yet the observer-threaded entry point — the one re-exported to benches under bench-internals and the subject of ADR 0006 — has no rustdoc at all. State the observer contract: events are emitted only when the handle is Some, and derived work is the adapter's responsibility.

As per coding guidelines, "Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc".

📝 Proposed doc comment
+/// Wraps inline Markdown `text` while forwarding classification events to
+/// `observer`.
+///
+/// `width` is measured in Unicode display columns. Events are emitted only
+/// when `observer` is `Some`; the handle carries no derived work, so any
+/// formatting or level gating belongs to the adapter behind it. Passing
+/// `&mut None` disables observation entirely and is the zero-overhead path
+/// exercised by the benchmarks.
 pub(in crate::wrap) fn wrap_preserving_code_observed(
     text: &str,
     width: usize,
     observer: &mut ObserverHandle<'_>,
 ) -> Vec<String> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(in crate::wrap) fn wrap_preserving_code_observed(
text: &str,
width: usize,
observer: &mut ObserverHandle<'_>,
) -> Vec<String> {
/// Wraps inline Markdown `text` while forwarding classification events to
/// `observer`.
///
/// `width` is measured in Unicode display columns. Events are emitted only
/// when `observer` is `Some`; the handle carries no derived work, so any
/// formatting or level gating belongs to the adapter behind it. Passing
/// `&mut None` disables observation entirely and is the zero-overhead path
/// exercised by the benchmarks.
pub(in crate::wrap) fn wrap_preserving_code_observed(
text: &str,
width: usize,
observer: &mut ObserverHandle<'_>,
) -> Vec<String> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wrap/inline/wrapping.rs` around lines 338 - 342, Add Rustdoc (`///`)
directly above `wrap_preserving_code_observed` documenting that events are
emitted only when the observer handle is `Some`, and that the adapter is
responsible for derived work. Match the contract detail and style of the sibling
`wrap_preserving_code` documentation.

Source: Coding guidelines

Comment on lines +62 to +67
Event::FragmentClassified { token, kind }
if tracing::enabled!(tracing::Level::DEBUG) =>
{
let (snippet, truncated) = trace_text_snippet(token);
debug!(token = %snippet, truncated, kind = ?kind, "fragment classified");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Stop emitting raw Markdown through tracing.

Bounding a snippet limits volume, not sensitivity: arbitrary Markdown can contain
credentials or personal data, and debug events are commonly exported and retained.

  • src/wrap/tracing_adapter.rs#L62-L67: replace token = %snippet with
    non-sensitive metadata such as token_length, kind, and truncated.
  • docs/adrs/0006-observer-boundary-for-tracing.md#L39-L46: remove the
    bounded-snippet policy and document metadata-only diagnostics.
  • docs/architecture.md#L625-L632: remove the bounded-snippet claim.
  • docs/developers-guide.md#L678-L685: replace the raw-token logging example.
  • docs/developers-guide.md#L753-L764: remove the token field from the event
    mapping.
📍 Affects 4 files
  • src/wrap/tracing_adapter.rs#L62-L67 (this comment)
  • docs/adrs/0006-observer-boundary-for-tracing.md#L39-L46
  • docs/architecture.md#L625-L632
  • docs/developers-guide.md#L678-L685
  • docs/developers-guide.md#L753-L764
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wrap/tracing_adapter.rs` around lines 62 - 67, Stop emitting Markdown
token contents from the Event::FragmentClassified tracing branch: replace the
token snippet field with non-sensitive metadata such as token_length, kind, and
truncated, and remove any now-unused snippet handling. Update
docs/adrs/0006-observer-boundary-for-tracing.md lines 39-46 to document
metadata-only diagnostics, docs/architecture.md lines 625-632 to remove the
bounded-snippet claim, docs/developers-guide.md lines 678-685 to replace the
raw-token example, and docs/developers-guide.md lines 753-764 to remove the
token field from the event mapping.

Source: Coding guidelines

Comment thread tests/bench_fixtures.rs
Comment on lines +31 to +49
#[test]
fn realistic_document_fixture_covers_and_preserves_constructs() {
let document = realistic_markdown_document();
let source = document.join("\n");
assert_covers_link_code_footnote(&source, "the document fixture");

let wrapped = wrap_text(&document, BENCH_WIDTH).join("\n");
assert_covers_link_code_footnote(&wrapped, "the wrapped document");
// The specific constructs survive wrapping intact.
assert!(
wrapped.contains("`inline-code-0`"),
"code span split: {wrapped}"
);
assert!(
wrapped.contains("(https://example.com/crate/0)"),
"link split: {wrapped}"
);
assert!(wrapped.contains("[^note0]"), "footnote split: {wrapped}");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert every generated construct, not only the first one.

The fixtures generate 60 paragraphs and 120 clauses, but Lines 40-48 and Lines 58-63 only verify the 0 item. A regression affecting later links, code spans, or footnote references will pass. Iterate over every generated index, or compare the complete expected construct set with the wrapped output in both tests.

Also applies to: 51-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bench_fixtures.rs` around lines 31 - 49, Update
realistic_document_fixture_covers_and_preserves_constructs to validate every
generated construct rather than only index 0. Iterate across all generated
paragraph/clause indices and assert each corresponding code span, link, and
footnote reference is present in both source and wrapped document checks,
preserving the existing failure context.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Isolate vendor-specific tracing from domain logic behind adapter boundaries

2 participants