Uh oh!
There was an error while loading. Please reload this page.
refactor(telemetry): generate the redaction floor from one declaration (backend#2378) - #803
Conversation
…n (backend#2378) The Collector's six OTTL `replace_pattern` calls were a hand port of client-runtime's `_LOG_REDACTIONS`, and the comment above them said so: "a cross-repo second copy that cannot be machine-checked from here". backend#1908 is what that cost -- the generic pattern anchored its keyword with a word boundary on both sides, so every environment-variable spelling of a secret (AZURE_CLIENT_SECRET=, MYSQL_PASSWORD=, DJANGO_SECRET_KEY=, AWS_SECRET_ACCESS_KEY=) fell straight through, in both copies, with passing tests on both sides. The rule is now declared once, in client-runtime/log_redactions.json. This chart vendors those bytes at client/log_redactions.json and GENERATES the statements from them with `.Files.Get`, so the hand port is gone. The render is byte-identical to develop's, pattern for pattern and replacement for replacement -- verified by diffing the rendered ConfigMap. Why vendored and not fetched: client-runtime is PRIVATE and GITHUB_TOKEN is repo-scoped, so a lint job here cannot read it without a credential, and a network read fails on exactly the day the network is what broke. scripts/tests/collector-redaction-derived.sh joins the REQUIRED drift job (17 guards now) and asks the two questions this repo CAN answer offline: are the vendored bytes the ones scripts/.log-redactions.sha256 pins, and is what the chart renders the declaration, pair for pair and in order. It fails closed on a missing file, a missing or empty pin, an unparseable render, or zero patterns. It complements rather than duplicates collector-redaction-floor.sh: provenance there, behaviour here. A faithfully rendered declaration that redacts nothing passes the new guard and fails the old one. The one question no offline check can answer -- has upstream moved on? -- goes to the existing cross-repo job, which already mints a scoped App token for client-runtime and already pins a ref. It gains the same three checks for this contract and is renamed "Cross-repo contract drift", since it now covers two. Chart content changed, so version and appVersion move 1.9.65 -> 1.9.66. Verified: helm unittest 550 passed; make drift 17 guards green; make lint clean; shellcheck -S warning -x clean on the new guard; gitleaks clean with no new allowlist entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s green (backend#2378) The cross-repo job failed exactly as designed -- `log_redactions.json is missing at 6293d15…` -- because the pinned client-runtime ref predates the declaration. That is the two-PR ritual made visible, and it also proved the whole lane works end to end in CI: the App token minted, the private repo checked out, and the absent file reported as a FINDING rather than as agreement. Having proved it, do not land it red. A red gate on a merged PR teaches people to skip the tier (CLAUDE.md rule 4), so the pin now points at the head of client-runtime#376, which carries the declaration. The ref is also generalised: it governs BOTH vendored copies now, the same way backend's scripts/.data-ingestors-ref governs its two schemas. The pin is at an unmerged commit and the file says so, with the instruction to re-point it at client-runtime's develop merge SHA once #376 lands. The bytes are identical either way -- envelope_contract.json, gen_envelope_vectors.py and node_sizing.py are unchanged between 6293d15 and this SHA (verified), so the envelope half of the job compares the same thing it did before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
… (backend#2378) Bugbot on #803, and it is right: the step named "The rendered redaction floor matches the vendored declaration" ran in a job that never installs helm, so collector-redaction-derived.sh printed [SKIP] for the render half and exited 0. The step went green having compared only the vendored bytes -- a check whose name claims more than its mechanism does, which is the whole defect class this PR exists to remove. Found in a mechanism I added in the same change; the point survives. Two fixes, because installing helm alone would leave the trap re-armable: * the job installs helm, same action and same pin as drift-checks.yaml and helm-ci.yaml. * the script treats a missing helm as an ERROR whenever REQUIRE_HELM or CI is set, and only skips on a dev box. So removing the setup step reddens instead of quietly halving the guard, and "cannot tell" is a finding everywhere it could otherwise become a green claim. Verified both paths: `PATH=/usr/bin:/bin CI=true` -> exit 1 with the strict message; the same without CI -> [SKIP], exit 0; `make drift` -> 17 guards green; shellcheck -S warning -x clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
DRIFT_GUARDS collided: develop added node-jsonpath-agreement.sh while this branch added collector-redaction-derived.sh. Both kept -- the derived guard sits beside collector-redaction-floor.sh, which it complements rather than replaces (the floor proves the patterns SCRUB, the derived one proves the render IS the declaration; dropping a redaction and fixing the digest reddens only the floor). Chart bumped 1.9.66 -> 1.9.67: develop reached 1.9.66 while this was open, so the version-bump gate needed one patch above it again. Verified after the merge rather than before: helm unittest 555 passed across 34 suites, make drift 18/18 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two collisions, and the second was not a formatting choice. DRIFT_GUARDS: develop added node-jsonpath-agreement.sh, this branch added collector-redaction-derived.sh. Both kept, the derived guard beside the floor guard it complements. scripts/.client-runtime-ref: develop bumped the pin to 48ccbac (backend#2221, the VM ceiling) while this branch pointed at 98f5ada, the then-head of client-runtime#376. CHECKED RATHER THAN CHOSEN -- git merge-base --is-ancestor says 48ccbac is NOT an ancestor of 98f5ada, so keeping ours would have silently regressed the vendored envelope contract to a version predating develop. Fixed upstream instead of papering here: merged client-runtime develop into #376 (clean, 88 tests green) and re-pointed this pin at its new head e4c4813, which now contains 48ccbac. Both vendored copies verified byte-identical against that SHA rather than assumed. Chart 1.9.66 -> 1.9.67; develop reached 1.9.66 while this was open. helm unittest 555 passed / 34 suites, make drift 18/18 green, after the merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b350035. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
saqlainsyed007
left a comment
There was a problem hiding this comment.
Reviewed at high effort. The core is correct — the template generation fails closed, the escaping round-trips (mustFromJson → quote/%q → OTTL literal → RE2), and collector-redaction-derived.sh is a genuine provenance+render check (digest against the pin, then rendered pairs vs. the declaration, order-sensitive, fail-closed on CI-without-helm). The Chart.yaml bump follows the repo's per-content-change convention, and the unmerged .client-runtime-ref pin is a documented post-#376 tidy-up (#376 is now approved).
Requesting changes on one confirmed defect — Bugbot's open "Unpinned Helm version" thread is valid, verified against the sibling files:
- The new
Set up Helmstep in.github/workflows/envelope-contract-drift.ymlusesazure/setup-helm@…v4.3.1with nowith: version:. drift-checks.yaml(version: v3.15.4) andhelm-ci.yaml(three steps, allversion: v3.15.4) pin it.azure/setup-helmdefaultsversiontolatest, so this job installs Helm 4.x while the requiredSource-of-truth driftpath — which runs the samecollector-redaction-derived.sh— runs v3.15.4.- That is exactly the "green having proved less than its name claims" risk this step was added to close: the render comparison could pass under a Helm the required path never uses (or Helm 4.x could change
.Files.Get/mustFromJson/templating outright). The step's comment "Same action, same pin as drift-checks.yaml and helm-ci.yaml" is now inaccurate.
Fix: add
with:
version: v3.15.4to that step so all three jobs render under the same pinned Helm. Everything else is good to go.
…end#2378) The step comment claimed "same action, same pin as drift-checks.yaml and helm-ci.yaml" while carrying no `version:` — so azure/setup-helm fell back to its `latest` default. This job would have compared renders under Helm 4.x while the REQUIRED `Source-of-truth drift` path runs the same script under v3.15.4: a green proving less than its name claims, which is the defect class this workflow exists to close. All eight setup-helm steps in the repo now report v3.15.4. Found by Bugbot, change requested by @saqlainsyed007. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 24, 2026
@saqlainsyed007 Fixed in uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1with:
version: v3.15.4You and Bugbot were right on every step of the reasoning, including the part I'd have been most likely to wave off: the step's own comment claimed "same action, same pin as drift-checks.yaml and helm-ci.yaml" while carrying no Verified all eight Re-requesting your review. |
Uh oh!
There was an error while loading. Please reload this page.
saadqbal
left a comment
There was a problem hiding this comment.
I verified this one end to end rather than reading it, because the whole PR is a provenance claim and
a provenance claim you haven't checked is just a longer comment.
The chain holds. Upstream client-runtime/log_redactions.json at the pinned ref, the vendoredclient/log_redactions.json, and the digest in scripts/.log-redactions.sha256 are all5a804b4c13a5ab05… — same bytes, same hash, three independent reads. And the render really is
unchanged: I rendered the chart at this head and at origin/develop and diffed the six extractedreplace_pattern statements — identical, patterns and replacements and order.
The new required guard is mutation-proof. Four mutations, all of them reddened, each with a
message that names the actual defect:
| mutation | result |
|---|---|
hand-write a 7th replace_pattern in the template | rendered but NOT declared: '\\bSNEAKY[0-9]{4}\\b' |
range reverse $redactions | same pairs, different ORDER — redaction is applied in sequence |
| hand-edit the vendored JSON | digest mismatch, both hashes printed |
| delete the vendored JSON | caught in step 1, before helm runs |
Also checked the thing that would have made all of the above moot: client/.helmignore does not
exclude log_redactions.json, and helm package puts it in the tgz — so the released artifact
carries the declaration and won't fail on a customer's install.
One finding, inline and non-blocking: 2>/dev/null on the helm template call discards the
template's fail message, so the .helmignore case — the exclusion your own comment names — reports
"expected exactly one Collector ConfigMap, found 0" one line after step 1 printed ok for that same
file. Fails closed, just points at the wrong thing.
Holding the approval on the gate, not on that: bats (bash unit, mocked) and Cursor Bugbot are
both still pending on this head, and @saqlainsyed007's change-request from b350035 is still
standing — the helm version pin they asked for is in and credited in the workflow comment, so
that's probably just a re-review away rather than anything outstanding.
Two notes for whoever does the post-merge tidy-up. The body says the pin is at #376's head98f5ada, but scripts/.client-runtime-ref actually holds e4c4813e… and #376's head is now7ea3886c — three SHAs. Harmless, and I confirmed why: the only commit between e4c4813 and7ea3886 touches tests/test_log_redaction_contract.py, and log_redactions.json is the identical
blob at both. So "the bytes are identical either way" is true. Just fix the SHA in the body so the
re-point instruction points somewhere real.
saqlainsyed007
left a comment
There was a problem hiding this comment.
Re-review. My original blocker is fixed — the Set up Helm step in envelope-contract-drift.yml now pins with: version: v3.15.4, so the drift job renders under the same Helm as the required path. Thanks.
Holding on @saadqbal's open thread, which I verified against the code and agree is a real defect (scripts/tests/collector-redaction-derived.sh:230):
helm template t "$CHART" ... 2>/dev/null | python3 "$CHK" "$DECLARATION"
The 2>/dev/null discards stderr, which is exactly where the template's fail writes. So when the fail-closed path fires — a missing/.helmignored log_redactions.json — the carefully-worded message that names the cause and the fix (the whole point of those fail strings) never reaches the operator; the guard sees empty stdin and reports its own generic error instead. saadqbal reproduced it via .helmignore. Drop the 2>/dev/null (or capture stderr and surface it on non-zero exit) so the actionable message survives. Everything else is good — happy to approve once that lands.
… (backend#2378) helm stderr went to /dev/null, so every `fail` string in the template -- written by hand to name the cause AND the fix -- was unreachable from the guard that triggers them. The cost is misdirection, not a fail-open. Step 1 checks the declaration on DISK and prints `ok`; the template could not READ it. So the two halves contradicted each other one line apart, and the only surviving message pointed at the ConfigMap -- sending a 2am reader through the wrong template while the actual cause was a .helmignore entry, the very exclusion the template comment calls out by name. Capture stderr and replay it on failure. Filters only the kubeconfig-permissions warnings; `|| true` because grep exits 1 on no match and an empty stderr must not become a different failure. Verified across three paths: green stays green; the reported case now prints "client/log_redactions.json is missing from the chart ... restore it and check scripts/.log-redactions.sha256"; and the digest-drift path is unchanged. Still exits 1 under pipefail throughout. Reported and reproduced by @saadqbal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 24, 2026
Fixed in Before ( After, same input — the template's own Your framing of why it matters is the part I'd have got wrong on my own: the Verified three paths rather than just the reported one, since a stderr change can mask a different failure:
Kept your One thing I did not fix, so it's a known limit rather than an oversight: the misdirecting You were right that this was the only thing worth blocking on. |
saqlainsyed007
left a comment
There was a problem hiding this comment.
Approving. Both blockers are resolved and verified:
- Helm version pin (my original) — the
Set up Helmstep now pinsv3.15.4, so the drift job renders under the same Helm as the required path. 2>/dev/nullswallowing the fail message (@saadqbal's) — the helm render now captures stderr to$HELM_ERRand, on failure, surfaces it (grep -vfilters only the benign kubeconfig WARNING,|| trueso an empty stderr doesn't change the failure), thenexit 1. So the template's actionablefailstrings reach the operator instead of a generic "found 0 ConfigMaps". Still fail-closed (pipefail exits 1) — purely a which-file-the-2am-reader-is-sent-to fix.
The core refactor was sound throughout (one vendored declaration, generated OTTL statements, digest tripwire, the render-vs-declaration guard). CI green, no open threads.
saadqbal
left a comment
There was a problem hiding this comment.
Approving. My finding is fixed and I re-ran the reproduction rather than reading the diff: withlog_redactions.json added to .helmignore, the guard now surfaces helm's own message —
"client/log_redactions.json is missing from the chart … restore it and check
scripts/.log-redactions.sha256" — so the operator is sent to the right file. Still exits 1, and the
mutation anchor still reddens on a hand-written 7th replace_pattern, so nothing was traded away for
the diagnosability.
The comment you left above the call is better than the fix — it records the misdirection, not just
the swallow, which is the part that would have cost someone an hour.
Worth one non-blocking note since it's the same line of thinking, and I'm explicitly not asking for
more here: python's found 0 ConfigMaps still prints before helm's captured stderr, so the first[ERROR] an operator reads remains the misleading one — the real cause is just two lines below it
now instead of absent. Inherent to the pipeline ordering, and vastly better than it was.
For the record, the earlier verification stands on this head too: upstream bytes, vendored copy and
pinned digest all agree, the render is byte-identical to develop's, and the declaration ships inside
the packaged tgz.
Uh oh!
There was an error while loading. Please reload this page.

Closes tracebloc/backend#2378 — the consumer half; tracebloc/client-runtime#376 is the owner half and merges first, so the keyword lives here.
What was wrong
The six OTTL
replace_patterncalls intelemetry-collector-configmap.yamlwere a hand port of client-runtime's_LOG_REDACTIONS, and the comment above them said so in as many words: "THIS IS A CROSS-REPO SECOND COPY AND IT CANNOT BE MACHINE-CHECKED FROM HERE."backend#1908 is what that cost. The generic pattern anchored its keyword with a word boundary on both sides, and
_is a word character, so every environment-variable-shaped secret —AZURE_CLIENT_SECRET=,MYSQL_PASSWORD=,DJANGO_SECRET_KEY=,AWS_SECRET_ACCESS_KEY=— fell straight through. In both copies. For as long as they existed. With passing tests on both sides.What this does
The rule is declared once, in
client-runtime/log_redactions.json. This chart vendors those bytes atclient/log_redactions.jsonand generates the statements from them with.Files.Get+mustFromJson. The hand port is gone.The render is byte-identical to develop's — same six patterns, same six replacements, same order — verified by diffing the rendered ConfigMap.
helm unittestneeded no assertion changes.Why vendored and not fetched
client-runtimeis private andGITHUB_TOKENis repo-scoped, so no lint job here can read it without a credential — and a network read fails on exactly the day the network is what broke. So the bytes are vendored with their sha256 and checked locally, offline, in the required drift job.Where the checks live, and what each can see
scripts/tests/collector-redaction-derived.sh(new, inmake drift→Source-of-truth drift)scripts/.log-redactions.sha256pins, and is what the chart renders the declaration — pair for pair, in order?scripts/tests/collector-redaction-floor.sh(existing, from #799)Cross-repo contract drift(existing job, extended)The two local guards are complementary, and this was measured, not assumed: dropping a redaction from the declaration and fixing the digest leaves the new guard green (the local pair is self-consistent) and reddens the floor guard on its count floor. A hand-written statement in the template does the opposite. Neither subsumes the other, so
collector-redaction-floor.shwas extended in prose only — its behaviour and its independently-written specimens are untouched.Derived, never restated (backend#1729 rule 1, and rule 9's corollary): the new guard holds no pattern text of its own. It compares a file against the real template's real output. That is precisely what copy (3) failed to do — it greped the render for six substrings it held itself, agreed with itself, and reported that as agreement with a file in another repository.
Fails closed everywhere
fails, so the chart cannot render a Collector with an emptystatementslist (which would report Ready and export every secret it read). Verified:Error: … client/log_redactions.json is missing from the chart ….replace_pattern, or zero patterns parsed → all findings.Mutation proof
Anchors asserted before each run:
client/log_redactions.json is not the file scripts/.log-redactions.sha256 pinsreplace_patterninto the templateHANDWRITTENrendered but NOT declared: '\bHANDWRITTEN[0-9]+\b'client/log_redactions.jsonNo such filescripts/.log-redactions.sha256The ritual cost, stated rather than discovered
A bump is now a two-PR sequence across repos: land it in
client-runtime(its digest tripwire refuses to let that half land quietly), then here — copy the file, copy the digest, bumpscripts/.client-runtime-ref, bumpChart.yaml. That is the price of removing the network and the credential from the required path, and it is written intoscripts/.log-redactions.sha256and into the cross-repo job's failure message so nobody has to rediscover it.Verification
No
.gitleaks.tomlentry was needed, and none was added: the vendored declaration holds regexes, not secret-shaped values. The existing path-scoped entry forcollector-redaction-floor.shis untouched.Chart.yamlversion+appVersion→1.9.66(equal, one patch above develop), since chart content changed.Note
Medium Risk
Touches the Collector’s secret-scrubbing pipeline (what leaves a customer cluster), but the six patterns themselves are unchanged; risk is mainly fail-closed template/CI wiring and a temporary pin to an unmerged client-runtime commit.
Overview
The Collector’s redaction floor is no longer a hand-copied set of OTTL
replace_patterncalls. The chart vendorsclient/log_redactions.json(from client-runtime) and generates the statements via.Files.Get/mustFromJson. A missing or empty declaration fails the render so a Collector cannot start with no scrubbing.A new required drift guard (
collector-redaction-derived.sh) checks the vendored bytes againstscripts/.log-redactions.sha256and that the rendered pairs match the declaration in order. The weekly cross-repo job now diffs that file and digest against the same pinned client-runtime ref as the envelope contract. Chart version is 1.9.67.Note:
scripts/.client-runtime-refcurrently points at an unmerged client-runtime#376 SHA on purpose; re-point it after that PR lands. Adopting an upstream redaction change is a two-PR bump (runtime first, then this repo).Reviewed by Cursor Bugbot for commit 42e65a2. Bugbot is set up for automated code reviews on this repo. Configure here.