Skip to content

docs(adr): specify Level 3 content capture activation and sandbox env denylist - #5947

Open
dhshah13 wants to merge 4 commits into
fullsend-ai:mainfrom
dhshah13:docs/l3-activation-adrs
Open

docs(adr): specify Level 3 content capture activation and sandbox env denylist#5947
dhshah13 wants to merge 4 commits into
fullsend-ai:mainfrom
dhshah13:docs/l3-activation-adrs

Conversation

@dhshah13

@dhshah13 dhshah13 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

ADR 0050 defined Level 3 — prompt/completion content in spans — as an explicit opt-in but left the activation semantics unspecified: which configuration enables it, what happens on partial configuration, and where content may flow. Two ADRs close that gap:

  • ADR 0084 — Level 3 content capture activation contract. Three independently-owned conditions (operator env opt-in per ADR 0081's plumbing model, per-agent harness consent under ADR 0080's placement rule, and an org-level endpoint allow-list in ADR 0082's shape whose entries record governance sign-off). Absent/off conditions leave runs metadata-only; an affirmative capture request that cannot satisfy the contract fails before sandbox creation. Content flows over OTLP only, passes through secret redaction with hits recorded as findings, and deliberately excludes reasoning/thinking text (a stated narrowing of 0050's sketch).
  • ADR 0085 — sandbox denylist for telemetry and runtime control variables. Extends the fix(#5832): strip OIDC credentials from user-controlled environments #5837 denylist surface to OTEL_*/CLAUDE_CODE_*/TRACEPARENT keys and host_files destinations, as pre-flight hard errors, so the activation contract cannot be bypassed from inside the sandbox. Closes a live gap independent of Level 3.

Living documents updated in the same PR per the writing-adrs skill: dated annotations on ADRs 0050 and 0021, the operator guide's Level 3 section rewritten to the contract (no ADR links, per the guide-link policy), an architecture.md Decided entry, and a cross-reference in the operational-observability problem doc.

ADR numbers verified free at commit time against main and in-flight PRs via skills/renumber-adr/scripts/inflight-adr-numbers.sh.

Implementation is tracked separately (issues to follow); every implementation PR merges inert until the contract's preconditions are met. Relates to #2862 and the Level 3 line item in ADR 0050.

Test plan

  • make lint — ADR statuses/numbers/frontmatter, docs link scope, and markdown link linters all pass
  • ADRs conform to the template and writing-adrs skill (one decision each, 69/57 content lines, Consequences as one-sentence bullets)

🤖 Generated with Claude Code

@dhshah13
dhshah13 requested a review from a team as a code owner August 5, 2026 18:53
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Define Level 3 content capture activation contract and sandbox telemetry denylist

📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Define a fail-closed, three-party activation contract for Level 3 span content capture.
• Add an ADR to prevent in-sandbox self-enablement via telemetry/control env vars and sourced files.
• Update observability docs to align with the contract and cross-reference related decisions.
Diagram

graph TD
  OperatorEnv["Operator env opt-in"] & HarnessConsent["Harness consent"] & OrgAllowlist["Org endpoint allowlist"] --> Gate{"Activation contract"} -->|"pass"| Sandbox["Sandbox run"] --> Assembly["Span content assembly"] --> Redactor["Secret redactor"] --> OTLP["OTLP export"]
  Gate -->|"fail"| Abort["Fail before sandbox"]
  Assembly --> JSONL["JSONL exporter (metadata-only)"]
  subgraph Legend
    direction LR
    _cfg["Config input"] ~~~ _dec{"Decision"} ~~~ _flow["Processing step"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Single operator env flag (status quo-style opt-in)
  • ➕ Simpler activation model and documentation
  • ➕ Less coordination across owners
  • ➖ High risk: a single variable can route sensitive content to arbitrary endpoints
  • ➖ Harder to enforce governance and non-production boundaries
  • ➖ Easier to bypass from within the sandbox without additional controls
2. Centralize all consent in org policy (no harness knob)
  • ➕ One place to audit/approve; simpler per-repo operations
  • ➕ Stronger centralized governance
  • ➖ Removes per-agent/repo sensitivity control and CODEOWNERS-based consent
  • ➖ Encourages overly broad enablement that may not fit all agents
3. Allow-list by full OTLP URL (host+scheme+path) instead of host-only
  • ➕ More precise control; reduces ambiguity when multiple collectors share a host
  • ➕ Can differentiate prod vs non-prod paths on same host
  • ➖ More operational churn if paths/ports change
  • ➖ Harder to express safely across varied collector setups; host-level may be the practical governance unit

Recommendation: Keep the PR’s three-condition, fail-closed activation contract plus sandbox denylist. It balances independent ownership (operator + content-owner + governance), prevents accidental enablement, and explicitly blocks the known in-sandbox bypass surface. Consider tightening the allow-list granularity to full URL only if host-level control proves insufficient in practice.

Files changed (7) +208 / -20

Documentation (7) +208 / -20
0021-jsonl-reasoning-trace-exposure.mdAnnotate secret redaction role for future Level 3 span content +8/-0

Annotate secret redaction role for future Level 3 span content

• Adds a dated annotation referencing ADR 0084. Clarifies that secret redaction is an output-side invariant check and that access control remains the primary boundary.

docs/ADRs/0021-jsonl-reasoning-trace-exposure.md

0050-distributed-tracing-instrumentation.mdRecord where Level 3 activation semantics are specified +8/-0

Record where Level 3 activation semantics are specified

• Adds a dated note pointing Level 3 activation semantics to ADR 0084. Also references ADR 0085 as closing the sandbox-side opt-in path around the Level 3 model.

docs/ADRs/0050-distributed-tracing-instrumentation.md

0084-level-3-content-capture-activation-contract.mdAdd ADR for Level 3 content capture activation contract +92/-0

Add ADR for Level 3 content capture activation contract

• Introduces an accepted ADR defining a three-condition activation gate (operator env opt-in, per-agent harness consent, and org allow-listed OTLP endpoint host). Specifies fail-closed behavior for capture requests, OTLP-only content flow, mandatory redaction with findings, and explicitly excludes reasoning/thinking capture.

docs/ADRs/0084-level-3-content-capture-activation-contract.md

0085-sandbox-environment-variable-denylist.mdAdd ADR extending sandbox denylist to telemetry/control variables +68/-0

Add ADR extending sandbox denylist to telemetry/control variables

• Adds an accepted ADR denying OTEL_*, CLAUDE_CODE_*, and TRACEPARENT injection via harness env and via sourced/consumed host_files destinations. Establishes preflight hard-error semantics to prevent bypassing Level 3 gating from inside the sandbox.

docs/ADRs/0085-sandbox-environment-variable-denylist.md

architecture.mdDocument decided Level 3 activation + sandbox denylist in observability section +1/-0

Document decided Level 3 activation + sandbox denylist in observability section

• Adds a Decided bullet summarizing the dual-consent + allow-list activation contract and the sandbox denylist enforcement. Links to ADRs 0084 and 0085 for the canonical decisions.

docs/architecture.md

distributed-tracing.mdRewrite Level 3 guide section to the activation contract +30/-19

Rewrite Level 3 guide section to the activation contract

• Replaces a single-env-var enablement description with the three-condition activation contract and fail-closed semantics. Clarifies OTLP-only content flow, mandatory secret redaction, metadata-only JSONL behavior, and that reasoning/thinking is excluded from capture.

docs/guides/infrastructure/distributed-tracing.md

operational-observability.mdCross-reference ADR 0084 from trace-granularity open question +1/-1

Cross-reference ADR 0084 from trace-granularity open question

• Extends the trace-granularity open question to reference ADR 0084’s activation decision. Clarifies that metadata-by-default with on-demand content is the chosen activation model, while granularity/retention remain open.

docs/problems/operational-observability.md

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Site preview

Preview: https://44b79836-site.fullsend-ai.workers.dev

Commit: c7d43c3a4f0f634abd51dc1d25103d7ce240d64f

… denylist

ADR 0050 defined Level 3 (content in spans, explicit opt-in) but left the
activation semantics unspecified. ADR 0084 records the activation contract:
dual consent (operator env opt-in + per-agent harness consent) plus an
allow-listed OTLP destination whose entries record governance sign-off,
fail-closed on unsatisfiable capture requests, content over OTLP export
only, redaction with findings, no reasoning capture. ADR 0085 extends the
sandbox env denylist to OTEL_/CLAUDE_CODE_/TRACEPARENT and host_files
destinations so the contract cannot be bypassed from inside the sandbox.

Living docs updated per the writing-adrs skill: annotations on ADRs 0050
and 0021, the operator guide's Level 3 section rewritten to the contract,
an architecture.md Decided entry, and a problem-doc cross-reference.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@qodo-code-review

qodo-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Endpoint allow-list underspecified ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
ADR 0084 introduces FULLSEND_CONTENT_CAPTURE_ALLOWED_ENDPOINTS and says it follows ADR 0082’s
allow-list “shape,” but ADR 0082 defines a different allow-list (WORKFLOW_HOST_REPOS) for mint
workflow refs and doesn’t specify endpoint-host parsing/matching rules. This leaves the new endpoint
allow-list gate underspecified (format, normalization, host vs host:port, wildcards, defaults, and
management/status surface).
Code

docs/ADRs/0084-level-3-content-capture-activation-contract.md[R52-55]

+3. **Allowlisted destination:** the resolved OTLP traces endpoint host must
+   appear in the `FULLSEND_CONTENT_CAPTURE_ALLOWED_ENDPOINTS` org variable,
+   following the [ADR 0082](0082-workflow-host-allow-list.md) allow-list
+   shape (default-minimal, CLI-managed, surfaced in status). With no
Relevance

●●● Strong

Team often asks ADRs to spell out exact semantics/limits; underspecified allow-list rules likely get
clarified.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ADR 0084 claims an endpoint-host allow-list follows ADR 0082’s shape, but ADR 0082 is specifically
about mint workflow-host allow-listing via WORKFLOW_HOST_REPOS, not OTLP endpoint hosts; therefore
ADR 0084 lacks concrete endpoint allow-list semantics on its own.

docs/ADRs/0084-level-3-content-capture-activation-contract.md[52-56]
docs/ADRs/0082-workflow-host-allow-list.md[48-60]

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

## Issue description
ADR 0084 depends on an org-level endpoint allow-list, but it does not fully define the semantics of that allow-list (value format and matching rules) and instead references ADR 0082, which governs a different allow-list domain.

## Issue Context
Because this allow-list is a security gate for Level 3 content export, ambiguity in parsing/matching/ownership can lead to inconsistent or incorrect implementations.

## Fix Focus Areas
- docs/ADRs/0084-level-3-content-capture-activation-contract.md[52-57]
- docs/ADRs/0082-workflow-host-allow-list.md[48-60]

## Suggested approach
- In ADR 0084, explicitly define:
 - the exact `FULLSEND_CONTENT_CAPTURE_ALLOWED_ENDPOINTS` value format (e.g., CSV hosts, JSON array, newline-separated).
 - the matching algorithm (host only vs host:port, case/IDNA rules, handling of schemes/paths, wildcard policy).
 - defaults and behavior when unset/empty.
 - how it’s managed and surfaced (CLI command names and status output), if that’s part of the contract.
- Keep the ADR 0082 reference only as a *precedent* (if desired), but do not rely on it for endpoint-specific semantics.

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


2. Reasoning capture contradiction ✓ Resolved 🐞 Bug ≡ Correctness
Description
The operator guide and ADR 0084 explicitly state Level 3 excludes reasoning/thinking text, but ADR
0050 still states Level 3 “enables LLM-judge evaluation scorers that need to read agent reasoning,”
creating conflicting guidance on what sensitive data Level 3 exports. This can mislead
implementers/operators about the data-scope and privacy implications of enabling Level 3.
Code

docs/guides/infrastructure/distributed-tracing.md[R154-157]

+When active, spans include system prompts, user messages, per-turn assistant
+text, tool arguments, and tool results, all passed through secret redaction
+before export. Reasoning/thinking text is not captured. Content flows only
+to the allow-listed OTLP endpoint; `run-telemetry.jsonl` stays
Relevance

●● Moderate

They prefer annotations over rewriting accepted ADRs; may add clarifying note, but scope/wording is
subjective.

PR-#5244
PR-#2465

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The updated guide and ADR 0084 clearly state reasoning/thinking text is not captured, while ADR 0050
still ties Level 3 to reasoning-dependent scorers, creating a cross-document contradiction about
Level 3’s contents.

docs/guides/infrastructure/distributed-tracing.md[154-158]
docs/ADRs/0084-level-3-content-capture-activation-contract.md[72-75]
docs/ADRs/0050-distributed-tracing-instrumentation.md[82-88]

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

## Issue description
Level 3’s scope is inconsistent across documentation: new contract docs say reasoning/thinking is not captured, while ADR 0050 still implies Level 3 supports reasoning-dependent use cases.

## Issue Context
ADR 0084 intentionally narrows ADR 0050’s earlier Level 3 sketch, but ADR 0050’s *Decision* section still contains reasoning-oriented wording that reads like the active contract.

## Fix Focus Areas
- docs/ADRs/0050-distributed-tracing-instrumentation.md[82-88]
- docs/ADRs/0084-level-3-content-capture-activation-contract.md[72-75]
- docs/guides/infrastructure/distributed-tracing.md[154-158]

## Suggested approach
- Update ADR 0050’s Level 3 bullets to match the current contract (or explicitly mark the old “reasoning” statement as superseded/narrowed by ADR 0084).
- Keep ADR 0084/guide as the source of truth; ensure ADR 0050 can’t be read as promising reasoning capture.

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



Informational

3. Planned callout lacks issue link 📜 Skill insight ≡ Correctness
Description
The > **Planned:** callout for Level 3 content capture does not include a link to the tracking
issue. This violates the requirement that planned (not-yet-implemented) features be explicitly
marked and linked to an issue for traceability.
Code

docs/guides/infrastructure/distributed-tracing.md[R130-131]

> **Planned:** Level 3 content capture is not yet implemented. This section
-> documents the telemetry contract.
+> documents the activation contract.
Relevance

● Weak

Exact precedent: adding an issue link to the Level 3 “Planned” callout was requested and rejected.

PR-#3903

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The guide explicitly marks Level 3 as planned but does not provide an issue link in the `>
**Planned:**` callout. The compliance rule requires the callout format to include a link to the
relevant issue.

docs/guides/infrastructure/distributed-tracing.md[128-132]
Skill: writing-user-docs

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 planned-feature callout for Level 3 content capture is missing a required issue link.

## Issue Context
Planned features must use the `> **Planned:**` blockquote format *and* include a link to the relevant issue.

## Fix Focus Areas
- docs/guides/infrastructure/distributed-tracing.md[128-132]

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


4. Guide in infrastructure/ directory 📜 Skill insight ⌂ Architecture
Description
docs/guides/infrastructure/distributed-tracing.md is a documentation guide but is not placed under
docs/guides/admin/ or docs/guides/user/ as required. This breaks the guide directory/audience
placement policy and makes the guide harder to classify and maintain.
Code

docs/guides/infrastructure/distributed-tracing.md[R128-131]

## Enabling content capture (Level 3)

> **Planned:** Level 3 content capture is not yet implemented. This section
-> documents the telemetry contract.
+> documents the activation contract.
Relevance

● Weak

Close precedent: requests to move non-admin/user guides out of disallowed subdirs were explicitly
rejected.

PR-#5454
PR-#5502
PR-#5457

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The compliance rule requires every guide under docs/guides/ to be placed in either admin/ or
user/. The modified guide remains at docs/guides/infrastructure/distributed-tracing.md, which
violates that directory constraint.

docs/guides/infrastructure/distributed-tracing.md[1-5]
Skill: writing-user-docs

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

## Issue description
`docs/guides/infrastructure/distributed-tracing.md` is under `docs/guides/infrastructure/`, but guides must live under either `docs/guides/admin/` or `docs/guides/user/` and target a single audience.

## Issue Context
This PR modifies the guide, so it must be brought into compliance with the guide placement policy.

## Fix Focus Areas
- docs/guides/infrastructure/distributed-tracing.md[1-5]

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


Grey Divider

Context used
✅ Compliance rules (platform): 54 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread docs/guides/infrastructure/distributed-tracing.md
Comment thread docs/ADRs/0084-level-3-content-capture-activation-contract.md Outdated
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…cope narrowing

Review feedback on the Level 3 activation contract:

- ADR 0082's allow-list governs mint workflow refs, so it cannot supply
  matching semantics for an OTLP endpoint allow-list. Define them in
  ADR 0084 instead: comma-separated host or host:port, matched exactly and
  case-insensitively, no wildcards — a wildcard entry would let an
  unreviewed subdomain receive content. ADR 0082 stays as precedent for the
  operational shape only.
- ADR 0050's Level 3 section still promises reasoning text to LLM-judge
  scorers. Extend its annotation to record that ADR 0084 narrows the
  captured scope, so a reader of ADR 0050 alone is not misled about what
  content Level 3 exports.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@dhshah13

dhshah13 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Both findings addressed in c7d43c3.

Allow-list underspecified: correct — ADR 0082 governs mint workflow refs, so it cannot supply matching semantics for an OTLP endpoint allow-list, and leaning on it left a security gate ambiguous. ADR 0084 now defines them directly: comma-separated host or host:port, matched exactly and case-insensitively, no wildcards and no scheme/path matching, unset or empty means no capture request can succeed. The no-wildcard rule is stated as a decision rather than an implementation detail — a wildcard entry would let an unreviewed subdomain receive content. ADR 0082 remains cited only as precedent for the operational shape (default-minimal, CLI-managed, surfaced in status). The guide mirrors the format.

Reasoning-capture contradiction: correct that a reader of ADR 0050 alone would still expect reasoning text. Since 0050 is accepted and its Decision cannot be rewritten, its annotation now records the narrowing explicitly — Level 3 serves the LLM-judge case with prompts, messages, and tool activity, and extending scope to reasoning requires a new ADR.

On the alternatives raised in the summary: full-URL allow-listing is worth revisiting if host-level granularity proves insufficient, but host-level matches the governance unit — sign-off is about which backend holds the content, not which path on it.

@waynesun09 waynesun09 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.

Review findings (7 items, all MEDIUM severity) — see inline comments for details.

activation semantics unspecified: which configuration enables it, what happens
on partial configuration, and where content may flow. Content is the
highest-sensitivity telemetry fullsend emits (proprietary source, PII, tool
output), and the sibling agentic-ci harness demonstrated the failure mode this

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.

[MEDIUM] Unverified "sibling agentic-ci harness" incident cited as established fact

The Context section states as settled fact: "the sibling agentic-ci harness demonstrated the failure mode this gap invites: content capture enabled unconditionally by default." No other ADR, problem doc, or design doc in this repo documents or references this incident — the only other "agentic-ci" hits in the repo are unrelated UI-rendering comparisons. This is an external, unverified claim about another project's behavior used to justify the strictness of this contract, with no citation a reader could check.

Suggestion: Either link to where this was actually observed/discussed (an issue, incident writeup, or the agentic-ci repo itself), or soften the claim to something reviewable, e.g. "a comparable harness has shipped content capture enabled unconditionally by default, which is the failure mode this contract exists to prevent," with a source link.

rather than degrading silently:

1. **Operator opt-in (env):** `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`
set to `true` (or the equivalent `span_only`); `false` or unset is off,

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.

[MEDIUM] Unverified span_only value for OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT

This states the operator opt-in env var accepts true "(or the equivalent span_only)" as if that is an already-established literal value per the upstream OTel GenAI semantic conventions v1.37.0 pinned by this ADR. The OTel GenAI instrumentation content-capture design documents a multi-mode enum (span-only/event-only/span-and-event style values), but that doesn't confirm this exact literal string ("span_only", this casing/spelling) is what this specific env var accepts — naming is known to vary by language/instrumentation and the spec is still evolving. ADR 0050 (which this builds on) only ever documented true/unset. The same claim is repeated verbatim in the operator guide, and this ADR states "any other value is a hard error," so an incorrect assumption here directly affects implementation behavior.

Suggestion: Before merging, verify against the actual OTel GenAI semantic-conventions spec/instrumentation source (pinned at v1.37.0) exactly which literal values this env var accepts (case and spelling). If span_only is not correct, drop the parenthetical or fix it; if correct, cite the source so implementers don't have to re-derive it.

sensitivity varies by agent and repository, and the harness file's
CODEOWNERS review is the consent mechanism — which is why this knob lives
on the harness rather than `config.yaml` under
[ADR 0080](0080-config-yaml-vs-agent-env-var-scope.md)'s placement rule.

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.

[MEDIUM] Citing ADR 0080's placement rule for a harness field shape ADR 0080 never defines

Condition 2 introduces telemetry.content_capture: true as a new structured harness field and justifies its placement by invoking "ADR 0080's placement rule." ADR 0080 draws a binary line between exactly two surfaces: a plain config.yaml field with no {AGENT}_ prefix (pipeline/dispatch policy), or an {AGENT}_-prefixed env var delivered via env.runner/env.sandbox per ADR 0049 (single-agent behavior tuning). telemetry.content_capture: true is neither — it's a structured, non-prefixed, non-env-var harness key, a third shape ADR 0080's decision text doesn't address (there's also no existing Telemetry/ContentCapture field precedent to fall back on). Citing ADR 0080 as though it already sanctions this field's exact placement stretches a rule that only covers two specific mechanisms.

Suggestion: Either scope the claim down ("a new harness field shape not covered by ADR 0080's two surfaces; chosen because...") and justify it directly, or use an {AGENT}_CONTENT_CAPTURE-style env var in env.runner/env.sandbox instead, which would let this condition ride the existing ADR 0080/0049 placement rule without stretching it.

Content flows over OTLP export only. `run-telemetry.jsonl` keeps its
documented metadata-only contract, enforced by an attribute allow-list at the
file exporter. Captured content is assembled post-iteration from the
transcript and harness inputs into OTel GenAI semantic-convention aggregated

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.

[MEDIUM] Full multi-turn content aggregated into one span attribute may hit backend/collector limits

The Decision commits to assembling "system prompts, user messages, per-turn assistant text, tool arguments, and tool results" (potentially file contents / command output) into a single OTel GenAI semantic-convention aggregated attribute per per-iteration agent span, for a whole run's content. Span attribute values are commonly size-limited by collectors/backends, and aggregating an entire multi-turn transcript's tool output into one attribute is exactly the kind of point where such a limit could silently truncate or drop content, or exceed backend ingestion limits. This isn't addressed anywhere in the ADR, and unlike ADR 0050 there's no "Deferred to implementation" list to park it in.

Suggestion: Note explicitly in the ADR (or add a deferred-to-implementation item) how the design handles per-backend attribute size limits — e.g., truncation policy, splitting across multiple attributes, or falling back to span events/log records if a size ceiling is hit — so implementers don't discover this only after building against the aggregated-attribute shape.


- Content cannot flow by accident: no single variable, file, or workflow
edit enables capture, and every implementation PR merges inert.
- The non-production restriction on content is mechanically enforced — the

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.

[MEDIUM] Dangling "non-production restriction" reference never established in the Decision

This Consequences bullet states "The non-production restriction on content is mechanically enforced — the allow-list, not prose, decides where content may go..." but no production/non-production distinction is defined anywhere in this ADR's Context or Decision ("production" doesn't appear outside this one bullet). The Decision only describes three conditions (operator env opt-in, harness consent, allow-listed endpoint host) with no production/non-production framing. A reader relying on Consequences would believe there's a policy restricting capture to non-production backends that the Decision never actually establishes.

Suggestion: Either add an explicit statement in the Decision establishing what "non-production" means (e.g., the allow-list is expected to hold only non-production endpoints, enforced via required sign-off) or reword this bullet to drop the "non-production" framing: "Content cannot flow to an arbitrary destination — the allow-list, not prose, decides where content may go, and every entry requires a recorded sign-off."

@@ -128,27 +128,39 @@ export OTEL_EXPORTER_OTLP_TRACES_HEADERS="authorization=Basic%20${CREDS_B64},x-m
## Enabling content capture (Level 3)

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.

[MEDIUM] "Guide-link policy" cited to justify omitting ADR links does not exist and contradicts current practice

The PR description says this rewritten Level 3 section has "no ADR links, per the guide-link policy." No such policy is documented anywhere in this repo — not in docs/contributing/adrs.md, not in the writing-adrs skill, not in AGENTS.md. It also contradicts existing practice: docs/guides/infrastructure/layered-config-reference.md, docs/guides/dev/e2e-testing.md, docs/guides/dev/testing-workflows.md, docs/guides/dev/behaviour-testing.md, and docs/guides/user/jira-integration.md all link directly to specific ADRs by number and path. This reads as an invented convention asserted as an established rule.

Suggestion: Either link ADR 0084/0085 from this Level 3 section (matching the convention used elsewhere in docs/guides/), or, if there's an intended distinction between reference guides (which link ADRs) and operator/how-to guides (which don't), document that rule in docs/contributing/ so it can be checked in future reviews instead of asserted per-PR.

## Annotations

**2026-08-05:** [ADR 0084](0084-level-3-content-capture-activation-contract.md)
applies the secret redactor to Level 3 span content as an output-side

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.

[MEDIUM] Annotation claims a consistency the ADR 0084 mechanism doesn't clearly establish

This annotation says applying the secret redactor to Level 3 content is "consistent with this ADR's framing: access control remains the security boundary, and a redaction hit is a breach signal, not the defense." But this ADR's actual mechanism (per its Consequences: "Credential detection in JSONL files surfaces isolation breaches... as incidents rather than silently redacting them") is binary and non-transformative — on a hit the entire JSONL is suppressed, content is never sanitized and shipped. ADR 0084's Level 3 mechanism reads differently: content "passes through secret redaction at assembly" and "the export path strips any content attribute lacking the redaction marker" — implying redaction actively gates what leaves over OTLP to an external, allow-listed-but-still-third-party endpoint. That makes the redactor function as a preventive control on the egress path, not merely a detector paired with access-control-as-the-real-boundary — arguably the opposite framing from this ADR. ADR 0084 also never states precisely what happens to content on a hit (drop the flagged attribute vs. mask-and-still-export), so this "consistent framing" claim can't actually be verified against ADR 0084's own text.

Suggestion: Either (a) have ADR 0084 specify precisely what happens to content on a redaction hit (drop just the flagged attribute vs. mask-and-still-export), or (b) soften this annotation to acknowledge that Level 3's export-to-external-backend context genuinely requires redaction to act as a defense, not merely a signal, since there's no post-hoc suppression option once content has left over OTLP the way there is for a stored JSONL file.

@waynesun09 waynesun09 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.

The activation contract's structure is sound — three gates with distinct owners, fail-closed, single fullsend-owned export pipeline. But verifying ADR 0085 against the current fullsend-ai/agents harnesses shows the denylist as written breaks every existing agent, and two of its guarantees rest on unstated assumptions. Four comments inline; the first is blocking-level, the rest are wording/spec-gap fixes.


- **Keys:** deny `OTEL_*`, `CLAUDE_CODE_*`, and `TRACEPARENT` among
`env.sandbox` keys, including forge-merged harness bases.
- **Destinations:** deny `host_files` destinations that the sandbox sources

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.

As written, this denylist hard-fails every existing harness in fullsend-ai/agents, and the migration path it assumes is circular:

  • Every agent harness mounts env files into the denied destination: common/env/gcp-vertex.env → /sandbox/workspace/.env.d/gcp-vertex.env, env/<agent>.env → .env.d/<agent>.env. Denying .env.d/* outright is a day-one pre-flight failure for all of them.
  • The natural migration is env.sandbox keys (ADR 0055's delivery) — but gcp-vertex.env exists to set CLAUDE_CODE_USE_VERTEX=1, and the CLAUDE_CODE_* prefix is denied there too. The variable that selects the inference backend is left with no valid configuration surface at all. The Consequences section says harnesses "must migrate to the supported configuration surfaces" but for this key no such surface exists.
  • Prefix-matching also confiscates legitimate use: CLAUDE_CODE_ENABLE_TELEMETRY + metrics export to a user's own collector is a documented runtime feature (cost tracking), unrelated to Level 3 content.

Suggestion: deny exact keys that carry content/export-routing risk (OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, OTEL_EXPORTER_OTLP_*, TRACEPARENT), and move runtime-provider/operational config behind fullsend-owned first-class knobs (e.g. runtime.provider: vertex, a telemetry: block) that the runner translates into env from trusted code — shipped before the deny takes effect. Otherwise this ADR contradicts the platform's own env delivery mechanism the moment it lands.


- Closes a live gap: a harness can no longer enable the runtime's native
content telemetry or redirect its export with no fullsend gate involved.
- ADR 0084's activation contract cannot be bypassed from inside the sandbox.

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.

This consequence is conditional on an assumption the ADR never states: a trusted sandbox image. image: is an unvalidated string in the harness schema — no allowlist, no digest pinning, and allowed_remote_resources covers composition URLs, not container images. A harness pointing at a custom image can bake OTEL_* into the image env, pre-populate .env.d/, or ship a doctored .claude/settings.json — all below this pre-flight's line of sight, making the bypass-from-inside claim false as written.

Either state the assumption as a consequence ("guarantees are conditional on a platform-trusted sandbox image; image trust is tracked separately") or add the missing control (image reference allowlist and/or digest pinning). Without one of those, the denylist's real value — collapsing the attack surface to a few loud, reviewable artifacts (image ref, policy hosts) instead of many quiet env lines — should be what this bullet claims, not impossibility.

3. **Allowlisted destination:** the resolved OTLP traces endpoint's host —
`host` or `host:port`, matched exactly and case-insensitively, no
wildcards and no scheme or path matching — must appear in the
comma-separated `FULLSEND_CONTENT_CAPTURE_ALLOWED_ENDPOINTS` org

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.

The org-variable gate has no owner in per-repo mode, and the ADR doesn't say what happens there. GitHub org variables don't exist for personal-account repos, and the repo-scoped CLI subcommands (correctly) can't manage org settings. So either Level 3 is silently impossible for per-repo installs (allow-list permanently empty), or an implementation falls back to a repo variable — which collapses gates 2 and 3 into the same owner and quietly deletes the dual-consent property this contract is built on.

Please state the per-repo story explicitly: either "Level 3 requires org-mode installation" as a listed consequence, or name who the second, non-repo owner is in per-repo mode. Also worth requiring allow-list entries to reference their governance sign-off record (rather than leaving the link to convention), since a bare host:port gives the approving admin nothing to evaluate.


Content flows over OTLP export only. `run-telemetry.jsonl` keeps its
documented metadata-only contract, enforced by an attribute allow-list at the
file exporter. Captured content is assembled post-iteration from the

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.

This sentence carries the design's most important invariant, and it's easy to miss mid-paragraph: content is assembled runner-side from the transcript fullsend already extracts — the runtime's native in-sandbox telemetry is never enabled, and ADR 0085 makes it unconstructible. Two careful readers of this PR independently first misread Level 3 as enabling Claude Code's own content capture inside the sandbox.

Suggest promoting it to a headline statement in the Decision (e.g. "The agent runtime's native content telemetry is never enabled; Level 3 re-exports the existing transcript through fullsend's own pipeline") so the single-pipeline property — no second exporter, no redaction bypass, no trace-identity drift — reads as the contract's foundation rather than an implementation detail.

@waynesun09
waynesun09 requested a review from rh-hemartin August 5, 2026 21:14
Nine findings from review, all accepted:

- Cite the agentic-ci content-on-by-default claim to its public source
  instead of asserting it as unreferenced fact.
- State that the capture variable's value set is fullsend-defined — the
  upstream conventions require opt-in but do not standardize values.
- Stop citing ADR 0080's placement rule for a harness-field shape it does
  not cover; justify the surface directly (per-agent review semantics).
- Define what happens on a redaction hit (mask and record a finding;
  encoding evasion drops the part whole) and state per-kind size budgets
  with backend-limit validation as a pilot precondition.
- Drop the dangling non-production framing from Consequences.
- State the per-repo story: the allow-list is an organization variable so
  its owner is distinct from CODEOWNERS; personal-account repositories
  cannot enable Level 3. Entries record a sign-off reference.
- Promote the single-pipeline invariant to a headline Decision statement:
  the runtime's native content telemetry is never enabled.
- ADR 0085: replace prefix denial with an enumerated denied set (content
  flags, OTEL_EXPORTER_*, trace identity) so provider selection and
  metrics-only telemetry via .env.d keep working — the previous text
  hard-failed every existing harness with no migration surface for
  CLAUDE_CODE_USE_VERTEX. .env.d mounting stays supported; its content is
  scanned for denied assignments. State the trusted-image assumption.
- ADR 0021 annotation: acknowledge redaction acts as a preventive egress
  control at Level 3, not only a breach signal.
- Guide: link ADRs 0084/0085 matching other guides' practice.

Reviewed-by findings: waynesun09 on PR fullsend-ai#5947.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@dhshah13

dhshah13 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

All findings addressed in 4311fa0. Point by point:

agentic-ci claim — cited to its public source: src/agentic_ci/harness.py sets the runtime's content-logging variables unconditionally (merged in opendatahub-io/agentic-ci#84).

span_only value — correct that upstream doesn't standardize it. The ADR now states the value set is fullsend-defined: the conventions require content capture to be opt-in but leave this variable's values to implementations; span_only is fullsend's alias, defined here, not inherited. Guide mirrors the wording.

ADR 0080 stretch — accepted. The claim is scoped down: a structured harness field is a third shape 0080's two surfaces don't cover, chosen because neither an org-wide config.yaml field nor an {AGENT}_ env var carries the per-agent CODEOWNERS review semantics this consent requires. The env-var alternative was considered and rejected for exactly that reason — workflow env has no per-agent review gate.

Attribute size limits — the Decision now states per-kind size budgets with structure-preserving truncation markers, and names backend/collector limit validation as a pilot precondition tracked in #5948.

Dangling non-production reference — reworded per your suggestion; the prod/non-prod sequencing lives in #5948's preconditions, not this ADR.

Guide-link policy — you're right that no such documented policy exists; I inferred it from f058f7d and overstated it. The Level 3 section now links ADRs 0084/0085, matching layered-config-reference and the other guides. (If maintainers do want a link-free rule for operator guides, that belongs in docs/contributing/ as you say — not asserted per-PR.)

0021 annotation — accepted, and 0084 now specifies hit behavior: a hit masks the matched value and records a security finding; encoding evasion drops the affected part whole. The annotation now acknowledges the real difference: on the OTLP egress path there is no post-hoc suppression, so redaction acts as a preventive control there, in addition to 0021's breach-signal role. Access control at the backend remains the primary boundary.

0085 circular migration — your strongest catch; the prefix denial was wrong. The Decision now denies an enumerated set only: the content-capture flags (exact keys), OTEL_EXPORTER_* (export routing), and TRACEPARENT/TRACESTATE. CLAUDE_CODE_USE_VERTEX, metrics-only telemetry, and .env.d/ mounting are explicitly untouched — the file scan gates what mounted env files may set, not whether they may exist. No existing harness in fullsend-ai/agents fails under the revised rule.

Trusted-image assumption — stated as a consequence: guarantees are conditional on a platform-trusted image; the denylist's value is collapsing the attack surface to a few loud, reviewable artifacts; image trust (allowlisting/digest pinning) is a separate control.

Per-repo owner — stated in the Decision: the allow-list is an organization variable precisely so its owner is distinct from the harness's CODEOWNERS; personal-account repositories have no second owner and cannot enable Level 3. Allow-list entries record a sign-off reference alongside the change.

Single-pipeline invariant — promoted to a headline Decision statement: "The agent runtime's native content telemetry is never enabled" — Level 3 re-exports the transcript fullsend already extracts through its own pipeline, and ADR 0085 makes the in-sandbox alternative unconstructible.

ADR 0084 is now 91 content lines — over the 80 target, all of it review-driven substance. If that reads as more than one decision, the pre-agreed fallback is splitting activation/gates from content-shape/redaction into a sibling ADR; the composite is defended first.

Compression only — every review-driven point survives with identical
meaning. The prior revision wrote each fix with a self-defending rationale
clause; ADR style states the decision with one clause of why.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants