Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions packages/runtime-node/src/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,19 @@
timer = setTimeout(() => resolve(false), timeoutMs);
});
const drained = Promise.allSettled(
targets.map((target) => target.forceFlush()),
).then(() => true);
const ok = await Promise.race([drained, timedOut]);
clearTimeout(timer);
targets.map((target) =>
Promise.resolve().then(() => target.forceFlush()),
),
).then((results) =>

Check failure on line 209 in packages/runtime-node/src/lifecycle.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Silent exception swallowing

`installAutterAutoFlush` still turns each `FlushTarget.forceFlush` into a promise and only inspects settled statuses. If a target implementation internally catches or absorbs exporter failures, this aggregate will treat the flush as successful and call `telemetryStats.markAllFlushed` for `activeFlushTargets` even though data was lost. Blast radius — if this failure path is hit it cascades to the downstream usage that depends on this file: functions `installAutterAutoFlush`, `unregisterFlushTargets`, `debugLog`, `CountingExporter.forceFlush`, `TelemetryStats.markCaptured`, `isDebugEnabled`, `redactAttributes`, `makeRedactor`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/core`, `@opentelemetry/sdk-trace-base`. Suggested fix: Require each flush target to preserve failure information from its internal exporter calls, or have this aggregate reject/return false whenever any target reports a hidden exporter failure instead of only checking the outer promise state. Blast radius — if this failure path is hit it cascades to the downstream usage that depends on this file: functions `installAutterAutoFlush`, `unregisterFlushTargets`, `debugLog`, `CountingExporter.forceFlush`, `TelemetryStats.markCaptured`, `isDebugEnabled`, `redactAttributes`, `makeRedactor`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/core`, `@opentelemetry/sdk-trace-base`.

Check failure on line 209 in packages/runtime-node/src/lifecycle.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Silent exception swallowing

`installAutterAutoFlush` still turns each `FlushTarget.forceFlush` into a promise and only inspects settled statuses. If a target implementation internally catches or absorbs exporter failures, this aggregate will treat the flush as successful and call `telemetryStats.markAllFlushed` for `activeFlushTargets` even though data was lost. Blast radius — if this failure path is hit it cascades to the downstream usage that depends on this file: functions `installAutterAutoFlush`, `unregisterFlushTargets`, `debugLog`, `CountingExporter.forceFlush`, `TelemetryStats.markCaptured`, `isDebugEnabled`, `redactAttributes`, `makeRedactor`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/core`, `@opentelemetry/sdk-trace-base`. Suggested fix: Require each flush target to preserve failure information from its internal exporter calls, or have this aggregate reject/return false whenever any target reports a hidden exporter failure instead of only checking the outer promise state. Blast radius — if this failure path is hit it cascades to the downstream usage that depends on this file: functions `installAutterAutoFlush`, `unregisterFlushTargets`, `debugLog`, `CountingExporter.forceFlush`, `TelemetryStats.markCaptured`, `isDebugEnabled`, `redactAttributes`, `makeRedactor`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/core`, `@opentelemetry/sdk-trace-base`.
results.every((result) => result.status === "fulfilled"),
);

let ok: boolean;
try {
ok = await Promise.race([drained, timedOut]);
} finally {
clearTimeout(timer);
}
if (ok) {
telemetryStats.markAllFlushed();
if (log) {
Expand Down
65 changes: 44 additions & 21 deletions packages/runtime-node/src/redact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,22 +131,46 @@
return out;
}

function redactValue(value: unknown, r: CompiledRedactor, depth: number): unknown {
if (typeof value === "string") return redactString(value, r);
if (Array.isArray(value)) {
if (depth <= 0) return value;
return value.map((item) => redactValue(item, r, depth - 1));
}
// Defensive: OTLP attributes are flat, but callers hand us arbitrary
// objects — walk one more level so nothing sensitive hides inside.
if (typeof value === "object" && value !== null && depth > 0) {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
out[k] = isSensitiveKey(k, r) ? r.mask : redactValue(v, r, depth - 1);
}
return out;
}
return value;
function redactValue(

Check warning on line 134 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · Missing CODEOWNERS reviewer approval

`redactAttributes`/`makeRedactor` changed in `packages/runtime-node/src/redact.ts`, but there is no approving reviewer from the code owner. This affects the exported redaction contract used by `captureException` and any downstream sinks that rely on the sanitized attribute shape. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`. Suggested fix: Request an approval from the CODEOWNERS owner for `packages/runtime-node/src/redact.ts` and keep that approval current on the latest revision. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`.

Check warning on line 134 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · Missing CODEOWNERS reviewer approval

`redactAttributes`/`makeRedactor` changed in `packages/runtime-node/src/redact.ts`, but there is no approving reviewer from the code owner. This affects the exported redaction contract used by `captureException` and any downstream sinks that rely on the sanitized attribute shape. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`. Suggested fix: Request an approval from the CODEOWNERS owner for `packages/runtime-node/src/redact.ts` and keep that approval current on the latest revision. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`.
value: unknown,
r: CompiledRedactor,
ancestors: WeakMap<object, object>,
): unknown {
if (typeof value === "string") return redactString(value, r);

if (Array.isArray(value)) {
const existing = ancestors.get(value);

Check failure on line 142 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Unbounded recursive redaction can overflow the stack on deep acyclic values

Cycle detection handles circular references, but removing `maxDepth` means `redactValue` now recursively walks every level of an acyclic object or array. A deeply nested runtime value passed to the exported redactor can exhaust the JavaScript call stack and throw into the host application. Retain a traversal-depth/node budget or use an iterative traversal while preserving cycle handling. Suggested fix: Preserve the previous depth limit while adding cycle detection: thread a `depth` parameter through `redactValue`, stop recursing when depth reaches 0, and only use the `WeakMap` to break cycles. Keep the existing whitelist/sensitive-key masking behavior unchanged. Blast radius — if this defect reaches production it can fail the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`.

Check failure on line 142 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Unbounded recursive redaction can overflow the stack on deep acyclic values

Cycle detection handles circular references, but removing `maxDepth` means `redactValue` now recursively walks every level of an acyclic object or array. A deeply nested runtime value passed to the exported redactor can exhaust the JavaScript call stack and throw into the host application. Retain a traversal-depth/node budget or use an iterative traversal while preserving cycle handling. Suggested fix: Preserve the previous depth limit while adding cycle detection: thread a `depth` parameter through `redactValue`, stop recursing when depth reaches 0, and only use the `WeakMap` to break cycles. Keep the existing whitelist/sensitive-key masking behavior unchanged. Blast radius — if this defect reaches production it can fail the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 [ai] Unbounded recursive redaction can overflow the stack on deep acyclic values — Risk: 75/100

Cycle detection handles circular references, but removing maxDepth means redactValue now recursively walks every level of an acyclic object or array. A deeply nested runtime value passed to the exported redactor can exhaust the JavaScript call stack and throw into the host application. Retain a traversal-depth/node budget or use an iterative traversal while preserving cycle handling.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Functions/symbols: redactAttributes, makeRedactor, redactString, redactValue, isSensitiveKey, redactWith, installAutterAutoFlush, initAutterServer
  • Dependent files: @opentelemetry/api
  • Scopes: @autter/runtime-node
🛠 AI fix prompt (copy & paste into your coding agent)
Preserve the previous depth limit while adding cycle detection: thread a `depth` parameter through `redactValue`, stop recursing when depth reaches 0, and only use the `WeakMap` to break cycles. Keep the existing whitelist/sensitive-key masking behavior unchanged. Blast radius — if this defect reaches production it can fail the downstream usage that depends on this file: functions `redactAttributes`, `makeRedactor`, `redactString`, `redactValue`, `isSensitiveKey`, `redactWith`, `installAutterAutoFlush`, `initAutterServer`; scopes `@autter/runtime-node`; dependent files `@opentelemetry/api`.

Flagged by Autter security & observability checks.

if (existing) return existing;

const out: unknown[] = [];
ancestors.set(value, out);

for (const item of value) {
out.push(redactValue(item, r, ancestors));

Check failure on line 149 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Unbounded redaction recursion can throw into capture callers

The changed redactValue recursively descends every nested array/object with no depth or work bound (line 149). Consequently a sufficiently deeply nested but acyclic caller-supplied Attributes value throws RangeError before redaction returns. initAutterServer installs the compiled redactor (line 719), and both its captureException and captureMessage callers spread activeRedactor(attributes) without a guard (lines 824 and 868); the captured error/message is therefore not emitted and the synchronous capture API can unexpectedly throw into application or global-error call chains. The previous contract bounded traversal at four levels. Suggested fix: Use an iterative traversal with an explicit depth/work limit, or restore a safe depth bound while preserving cycle handling. Ensure values beyond the limit are returned or replaced safely so activeRedactor never throws for arbitrary Attributes.

Check failure on line 149 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Unbounded recursive redaction can overflow on caller-supplied attributes

The new deep traversal recurses once per array/object nesting level with no depth or work bound. `redactAttributes` and the compiled `activeRedactor` both invoke this path for caller-provided attributes; notably `initAutterServer` configures it at server startup and `captureException` passes its attributes into it before creating the span. An acyclic object (or array) nested beyond the JavaScript stack limit therefore throws `RangeError` at this recursive call instead of returning safe attributes, causing the telemetry capture path itself to fail. The WeakMap only terminates cycles and does not protect this reachable acyclic input. Suggested fix: Bound redaction traversal with a maximum depth/work budget, or rewrite it iteratively. Preserve cycle handling and ensure values beyond the budget are returned or replaced safely without throwing.

Check failure on line 149 in packages/runtime-node/src/redact.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · Unbounded recursive redaction can throw during telemetry capture

The new deep traversal calls `redactValue` recursively for every nested array element and object property with no depth or work limit. A caller can supply an acyclic attribute nested beyond the JavaScript stack limit; that produces `RangeError` before `redactAttributes` returns. The active capture path spreads `activeRedactor(attributes)` while creating error spans (server.ts:819-825), so this failure escapes `captureException` and prevents the exception from being recorded. WeakMap only breaks cycles and cannot protect an arbitrarily deep acyclic value. Suggested fix: Replace recursive traversal with an iterative walk bounded by an explicit depth/work budget, or restore a safe maximum depth. Preserve the copy/no-mutation and cycle behavior, and make inputs beyond the budget resolve to a safe non-throwing value that cannot leak nested secrets.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 [ai] Unbounded redaction recursion can throw into capture callers — Risk: 79/100

The changed redactValue recursively descends every nested array/object with no depth or work bound (line 149). Consequently a sufficiently deeply nested but acyclic caller-supplied Attributes value throws RangeError before redaction returns. initAutterServer installs the compiled redactor (line 719), and both its captureException and captureMessage callers spread activeRedactor(attributes) without a guard (lines 824 and 868); the captured error/message is therefore not emitted and the synchronous capture API can unexpectedly throw into application or global-error call chains. The previous contract bounded traversal at four levels.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-node/src/server.ts, packages/runtime-node/src/redact.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Use an iterative traversal with an explicit depth/work limit, or restore a safe depth bound while preserving cycle handling. Ensure values beyond the limit are returned or replaced safely so activeRedactor never throws for arbitrary Attributes.

Flagged by Autter security & observability checks.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 [ai] Unbounded recursive redaction can overflow on caller-supplied attributes — Risk: 78/100

The new deep traversal recurses once per array/object nesting level with no depth or work bound. redactAttributes and the compiled activeRedactor both invoke this path for caller-provided attributes; notably initAutterServer configures it at server startup and captureException passes its attributes into it before creating the span. An acyclic object (or array) nested beyond the JavaScript stack limit therefore throws RangeError at this recursive call instead of returning safe attributes, causing the telemetry capture path itself to fail. The WeakMap only terminates cycles and does not protect this reachable acyclic input.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-node/src/redact.ts, packages/runtime-node/src/server.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Bound redaction traversal with a maximum depth/work budget, or rewrite it iteratively. Preserve cycle handling and ensure values beyond the budget are returned or replaced safely without throwing.

Flagged by Autter security & observability checks.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 [ai] Unbounded recursive redaction can throw during telemetry capture — Risk: 78/100

The new deep traversal calls redactValue recursively for every nested array element and object property with no depth or work limit. A caller can supply an acyclic attribute nested beyond the JavaScript stack limit; that produces RangeError before redactAttributes returns. The active capture path spreads activeRedactor(attributes) while creating error spans (server.ts:819-825), so this failure escapes captureException and prevents the exception from being recorded. WeakMap only breaks cycles and cannot protect an arbitrarily deep acyclic value.

⚠ Downstream affected — if this fails, it cascades to the usage that depends on this file:

  • Dependent files: packages/runtime-node/src/redact.ts, packages/runtime-node/src/server.ts
🛠 AI fix prompt (copy & paste into your coding agent)
Replace recursive traversal with an iterative walk bounded by an explicit depth/work budget, or restore a safe maximum depth. Preserve the copy/no-mutation and cycle behavior, and make inputs beyond the budget resolve to a safe non-throwing value that cannot leak nested secrets.

Flagged by Autter security & observability checks.

}

ancestors.delete(value);
return out;
}

if (typeof value === "object" && value !== null) {
const existing = ancestors.get(value);
if (existing) return existing;

const out: Record<string, unknown> = {};
ancestors.set(value, out);

for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
out[k] = isSensitiveKey(k, r)
? r.mask
: redactValue(v, r, ancestors);
}

ancestors.delete(value);
return out;
}

return value;
}

function isSensitiveKey(key: string, r: CompiledRedactor): boolean {
Expand All @@ -164,21 +188,20 @@
options?: RedactOptions,
): Attributes {
const r = compile(options);
return redactWith(attributes, r, 4);
return redactWith(attributes, r);
}

function redactWith(
attributes: Attributes | null | undefined,
r: CompiledRedactor,
maxDepth: number,
r: CompiledRedactor,
): Attributes {
const out: Attributes = {};
if (!attributes) return out;
for (const [key, value] of Object.entries(attributes)) {
if (value === undefined) continue;
out[key] = isSensitiveKey(key, r)
? r.mask
: (redactValue(value, r, maxDepth) as Attributes[string]);
: (redactValue(value, r, new WeakMap<object, object>()) as Attributes[string]);
}
return out;
}
Expand All @@ -194,5 +217,5 @@
return (attributes) => ({ ...(attributes ?? {}) });
}
const r = compile(options === true ? undefined : options);
return (attributes) => redactWith(attributes, r, 4);
return (attributes) => redactWith(attributes, r);
}
20 changes: 15 additions & 5 deletions packages/runtime-node/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -904,15 +904,25 @@

// Everything that buffers telemetry in-process, reachable as one unit:
// auto-flush and the crash monitor push all of these out together
// (NodeSDK exposes no forceFlush(), but the processors we handed it do).

Check failure on line 907 in packages/runtime-node/src/server.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · PR mixes refactor and behavior change

This PR mixes a behavior fix for auto-flush failure reporting with a separate behavior change in redaction logic. `packages/runtime-node/src/server.ts` and `packages/runtime-node/src/lifecycle.ts` are the files contributing most to the mix. Split into one PR for the runtime-node auto-flush failure propagation in `packages/runtime-node/src/server.ts` and `packages/runtime-node/src/lifecycle.ts` plus `packages/runtime-node/test/lifecycle.test.mjs`, and a second PR for the recursive/circular attribute redaction changes in `packages/runtime-node/src/redact.ts` with `packages/runtime-node/test/redact.test.mjs`. The server flush path is higher risk because `packages/runtime-node/src/server.ts` has blastRadiusScore 45 and fanIn 1, so it deserves isolated review. Blast radius — if the refactor introduces a regression the behavior change masks it on: functions `initAutterServer`, `recordLlmCall`, `captureException`, `ErrorTraceRetentionProcessor.forceFlush`, `ErrorTraceRetentionProcessor.flush`, `runWithSpan`, `installAutterAutoFlush`, `redactAttributes`; scopes `@autter/runtime-node`; dependent files `./lifecycle.js`, `./redact.js`, `@opentelemetry/api`, `@opentelemetry/core`, `@opentelemetry/exporter-metrics-otlp-http`, `@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/instrumentation-http`, `@opentelemetry/resources`. Suggested fix: Split this PR so refactors land separately from behavior changes. Pure-refactor PRs should preserve behavior (no test changes beyond renames). Behavior-change PRs should focus on a single new capability. Start by extracting `packages/runtime-node/src/server.ts`'s refactor portion (or its behavior portion, whichever is smaller) into its own PR.

Check failure on line 907 in packages/runtime-node/src/server.ts

View check run for this annotation

Autter.dev / autter/review-gate

🔴 High · PR mixes refactor and behavior change

This PR mixes a behavior fix for auto-flush failure reporting with a separate behavior change in redaction logic. `packages/runtime-node/src/server.ts` and `packages/runtime-node/src/lifecycle.ts` are the files contributing most to the mix. Split into one PR for the runtime-node auto-flush failure propagation in `packages/runtime-node/src/server.ts` and `packages/runtime-node/src/lifecycle.ts` plus `packages/runtime-node/test/lifecycle.test.mjs`, and a second PR for the recursive/circular attribute redaction changes in `packages/runtime-node/src/redact.ts` with `packages/runtime-node/test/redact.test.mjs`. The server flush path is higher risk because `packages/runtime-node/src/server.ts` has blastRadiusScore 45 and fanIn 1, so it deserves isolated review. Blast radius — if the refactor introduces a regression the behavior change masks it on: functions `initAutterServer`, `recordLlmCall`, `captureException`, `ErrorTraceRetentionProcessor.forceFlush`, `ErrorTraceRetentionProcessor.flush`, `runWithSpan`, `installAutterAutoFlush`, `redactAttributes`; scopes `@autter/runtime-node`; dependent files `./lifecycle.js`, `./redact.js`, `@opentelemetry/api`, `@opentelemetry/core`, `@opentelemetry/exporter-metrics-otlp-http`, `@opentelemetry/exporter-trace-otlp-http`, `@opentelemetry/instrumentation-http`, `@opentelemetry/resources`. Suggested fix: Split this PR so refactors land separately from behavior changes. Pure-refactor PRs should preserve behavior (no test changes beyond renames). Behavior-change PRs should focus on a single new capability. Start by extracting `packages/runtime-node/src/server.ts`'s refactor portion (or its behavior portion, whichever is smaller) into its own PR.
const flushTarget: FlushTarget = {
forceFlush: async () => {
await Promise.allSettled([
alwaysOnProvider.forceFlush(),
mainSpanProcessor.forceFlush(),
...(errorTraceBuffer ? [errorTraceBuffer.forceFlush()] : []),
metricReader.forceFlush(),
const results = await Promise.allSettled([

Check warning on line 910 in packages/runtime-node/src/server.ts

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · `packages/runtime-node/README.md` usually changes with this file

`packages/runtime-node/src/server.ts` and `packages/runtime-node/README.md` changed together in 6 of the last 6 commits that touched either (100%), but `packages/runtime-node/README.md` is not in this PR. This is history, not a rule — if the coupling no longer applies, ignore it. It most often means a matching change was missed (a caller, a type, a fixture, a migration's rollback). Suggested fix: In this repository, `packages/runtime-node/src/server.ts` and `packages/runtime-node/README.md` have historically changed together (6 shared commits, 100% co-change rate). The current change modifies `packages/runtime-node/src/server.ts` only. Open `packages/runtime-node/README.md` and determine whether it needs a corresponding change. If it does, make it. If it genuinely does not, explain why the coupling no longer holds.

Check warning on line 910 in packages/runtime-node/src/server.ts

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · `packages/runtime-node/README.md` usually changes with this file

`packages/runtime-node/src/server.ts` and `packages/runtime-node/README.md` changed together in 6 of the last 6 commits that touched either (100%), but `packages/runtime-node/README.md` is not in this PR. This is history, not a rule — if the coupling no longer applies, ignore it. It most often means a matching change was missed (a caller, a type, a fixture, a migration's rollback). Suggested fix: In this repository, `packages/runtime-node/src/server.ts` and `packages/runtime-node/README.md` have historically changed together (6 shared commits, 100% co-change rate). The current change modifies `packages/runtime-node/src/server.ts` only. Open `packages/runtime-node/README.md` and determine whether it needs a corresponding change. If it does, make it. If it genuinely does not, explain why the coupling no longer holds.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 [deterministic] packages/runtime-node/README.md usually changes with this file — Risk: 60/100

packages/runtime-node/src/server.ts and packages/runtime-node/README.md changed together in 6 of the last 6 commits that touched either (100%), but packages/runtime-node/README.md is not in this PR.

This is history, not a rule — if the coupling no longer applies, ignore it. It most often means a matching change was missed (a caller, a type, a fixture, a migration's rollback).

🛠 AI fix prompt (copy & paste into your coding agent)
In this repository, `packages/runtime-node/src/server.ts` and `packages/runtime-node/README.md` have historically changed together (6 shared commits, 100% co-change rate). The current change modifies `packages/runtime-node/src/server.ts` only. Open `packages/runtime-node/README.md` and determine whether it needs a corresponding change. If it does, make it. If it genuinely does not, explain why the coupling no longer holds.

Flagged by Autter security & observability checks.

Promise.resolve().then(() => alwaysOnProvider.forceFlush()),
Promise.resolve().then(() => mainSpanProcessor.forceFlush()),
...(errorTraceBuffer
? [Promise.resolve().then(() => errorTraceBuffer.forceFlush())]
: []),
Promise.resolve().then(() => metricReader.forceFlush()),
]);

const failed = results.find(
(result) => result.status === "rejected",
);

if (failed) {
throw failed.reason;
}
},
};
registerFlushTarget("active-server", flushTarget);
Expand Down
100 changes: 100 additions & 0 deletions packages/runtime-node/test/lifecycle.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import test from "node:test";
import assert from "node:assert/strict";
import { installAutterAutoFlush } from "../dist/index.js";

test("flush reports false when a target rejects", async () => {

Check warning on line 5 in packages/runtime-node/test/lifecycle.test.mjs

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · Missing CODEOWNERS reviewer approval

The new regression coverage for `installAutterAutoFlush.flush` and the built-in-style flush target was added without any approving reviewer from the relevant owner set. Because this test guards the public shutdown behavior, it should be reviewed alongside the implementation change it protects. Suggested fix: Obtain an approving review from the owner responsible for `packages/runtime-node` changes before merging.

Check warning on line 5 in packages/runtime-node/test/lifecycle.test.mjs

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · Missing CODEOWNERS reviewer approval

The new regression coverage for `installAutterAutoFlush.flush` and the built-in-style flush target was added without any approving reviewer from the relevant owner set. Because this test guards the public shutdown behavior, it should be reviewed alongside the implementation change it protects. Suggested fix: Obtain an approving review from the owner responsible for `packages/runtime-node` changes before merging.
const failingTarget = {
forceFlush() {
return Promise.reject(new Error("flush failed"));
},
};

const handle = installAutterAutoFlush({
targets: [failingTarget],
log: false,
warnOnUnflushedExit: false,
});

const result = await handle.flush("reproduction");
handle.dispose();

assert.equal(result, false);
});

test("flush reports false when a target throws synchronously", async () => {
const failingTarget = {
forceFlush() {
throw new Error("sync flush failed");
},
};

const handle = installAutterAutoFlush({
targets: [failingTarget],
log: false,
warnOnUnflushedExit: false,
});

const result = await handle.flush("sync-throw");
handle.dispose();

assert.equal(result, false);
});

test("flush reports true when all targets fulfill", async () => {
const firstTarget = {
forceFlush() {
return Promise.resolve();
},
};

const secondTarget = {
forceFlush() {
return Promise.resolve();
},
};

const handle = installAutterAutoFlush({
targets: [firstTarget, secondTarget],
log: false,
warnOnUnflushedExit: false,
});

const result = await handle.flush("success");
handle.dispose();

assert.equal(result, true);
});

test("flush reports false when a built-in-style target propagates an exporter rejection", async () => {
const exporter = {
forceFlush() {
return Promise.reject(new Error("exporter failed"));
},
};

const builtInStyleTarget = {
async forceFlush() {
const results = await Promise.allSettled([
exporter.forceFlush(),
Promise.resolve(),
]);

const failed = results.find((result) => result.status === "rejected");

if (failed) {
throw failed.reason;
}
},
};

const handle = installAutterAutoFlush({
targets: [builtInStyleTarget],
log: false,
warnOnUnflushedExit: false,
});

const result = await handle.flush("built-in-style-rejection");
handle.dispose();

assert.equal(result, false);
});
35 changes: 35 additions & 0 deletions packages/runtime-node/test/redact.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,38 @@
assert.deepEqual(redactAttributes(), {});
assert.deepEqual(redactAttributes(null), {});
});

test("redacts sensitive keys beyond the nested traversal depth", () => {

Check warning on line 128 in packages/runtime-node/test/redact.test.mjs

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · Missing CODEOWNERS reviewer approval

The added `redactAttributes` regression tests cover nested and circular attribute handling, but there is no approving reviewer from the file owner. These tests anchor the exported redaction behavior consumed by `captureException` and server-side attribute processing. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: scopes `@autter/runtime-node`; dependent files `../dist/index.js`, `node:assert/strict`, `node:test`. Suggested fix: Request approval from the CODEOWNERS owner covering `packages/runtime-node/test/redact.test.mjs`. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: scopes `@autter/runtime-node`; dependent files `../dist/index.js`, `node:assert/strict`, `node:test`.

Check warning on line 128 in packages/runtime-node/test/redact.test.mjs

View check run for this annotation

Autter.dev / autter/review-gate

🟠 Medium · Missing CODEOWNERS reviewer approval

The added `redactAttributes` regression tests cover nested and circular attribute handling, but there is no approving reviewer from the file owner. These tests anchor the exported redaction behavior consumed by `captureException` and server-side attribute processing. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: scopes `@autter/runtime-node`; dependent files `../dist/index.js`, `node:assert/strict`, `node:test`. Suggested fix: Request approval from the CODEOWNERS owner covering `packages/runtime-node/test/redact.test.mjs`. Blast radius — skipping this guardrail cascades to the downstream usage that depends on this file: scopes `@autter/runtime-node`; dependent files `../dist/index.js`, `node:assert/strict`, `node:test`.
const out = redactAttributes({
context: {
level1: {
level2: {
level3: {
level4: {
password: "SECRET",
},
},
},
},
},
});

assert.equal(
out.context.level1.level2.level3.level4.password,
MASK,
);
});

test("handles circular references without leaking sensitive values", () => {
const context = {};
const nested = { password: "SECRET", safe: "ok" };

context.self = context;
context.nested = nested;

const out = redactAttributes({ context });

assert.equal(out.context.nested.password, MASK);
assert.equal(out.context.nested.safe, "ok");
assert.equal(out.context.self, out.context);
});