History

[codex] Redact agent awareness relay diagnostics (#3410)

Julius Marminge committed 4 months ago

Commit 025fd6f1 parent 52701262 files · +91 −7Browse files

Unified · Split

2 files
  1. apps/server/src/relay/AgentAwarenessRelay.test.ts +45 −0
  2. apps/server/src/relay/AgentAwarenessRelay.ts +46 −7
apps/server/src/relay/AgentAwarenessRelay.test.ts+45−0
@@ -20,6 +20,7 @@ import { CommandId, ProviderInstanceId } from "@t3tools/contracts"; · 19 unmodified lines
20import { RelayClientTracer } from "@t3tools/shared/relayTracing";20import { RelayClientTracer } from "@t3tools/shared/relayTracing";
21import { RELAY_ACTIVITY_PUBLISH_TYP, verifyRelayJwt } from "@t3tools/shared/relayJwt";21import { RELAY_ACTIVITY_PUBLISH_TYP, verifyRelayJwt } from "@t3tools/shared/relayJwt";
22import { describe, expect, it } from "@effect/vitest";22import { describe, expect, it } from "@effect/vitest";
23import * as Cause from "effect/Cause";
23import * as Deferred from "effect/Deferred";24import * as Deferred from "effect/Deferred";
24import * as Effect from "effect/Effect";25import * as Effect from "effect/Effect";
25import * as Layer from "effect/Layer";26import * as Layer from "effect/Layer";
@@ -98,6 +99,50 @@ function makeMemorySecretStore() { · 72 unmodified lines
98}99}
99 100
100describe.sequential("signRelayAgentActivityPublishProof", () => {101describe.sequential("signRelayAgentActivityPublishProof", () => {
102 it("redacts relay URL secrets from log attributes", () => {
103 const relayUrl =
104 "https://relay-user:relay-password@relay.example.test/private/path?token=relay-secret#fragment";
105 const attributes = AgentAwarenessRelay.relayUrlLogAttributes(relayUrl);
106
107 expect(attributes).toEqual({
108 relayUrlConfigured: true,
109 relayUrlInputLength: relayUrl.length,
110 relayUrlProtocol: "https:",
111 relayUrlHostname: "relay.example.test",
112 });
113 const renderedAttributes = Object.values(attributes).join(" ");
114 expect(renderedAttributes).not.toContain("relay-user");
115 expect(renderedAttributes).not.toContain("relay-password");
116 expect(renderedAttributes).not.toContain("private/path");
117 expect(renderedAttributes).not.toContain("relay-secret");
118 expect(renderedAttributes).not.toContain("fragment");
119 });
120
121 it("summarizes publish causes without serializing nested failures or defects", () => {
122 const privateFailureDetail = "private relay response body";
123 const privateDefectDetail = "private relay defect detail";
124 const cause = Cause.combine(
125 Cause.fail({
126 _tag: "RelayPublishError",
127 cause: new Error(privateFailureDetail),
128 detail: privateFailureDetail,
129 }),
130 Cause.die(new Error(privateDefectDetail)),
131 );
132 const attributes = AgentAwarenessRelay.relayPublishCauseLogAttributes(cause);
133
134 expect(attributes).toEqual({
135 causeReasonCount: 2,
136 causeFailureCount: 1,
137 causeDefectCount: 1,
138 causeInterruptionCount: 0,
139 causeFailureTags: ["RelayPublishError"],
140 });
141 const renderedAttributes = Object.values(attributes).join(" ");
142 expect(renderedAttributes).not.toContain(privateFailureDetail);
143 expect(renderedAttributes).not.toContain(privateDefectDetail);
144 });
145
101 it("distinguishes pending link credentials from disabled publication", () => {146 it("distinguishes pending link credentials from disabled publication", () => {
102 expect(147 expect(
103 AgentAwarenessRelay.resolveAgentActivityPublishingStartupState({148 AgentAwarenessRelay.resolveAgentActivityPublishingStartupState({
apps/server/src/relay/AgentAwarenessRelay.ts+46−7
@@ -18,6 +18,7 @@ import { · 17 unmodified lines
18 RELAY_ACTIVITY_PUBLISH_TYP,18 RELAY_ACTIVITY_PUBLISH_TYP,
19 signRelayJwt,19 signRelayJwt,
20} from "@t3tools/shared/relayJwt";20} from "@t3tools/shared/relayJwt";
21import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics";
21import * as Cause from "effect/Cause";22import * as Cause from "effect/Cause";
22import * as Context from "effect/Context";23import * as Context from "effect/Context";
23import * as Crypto from "effect/Crypto";24import * as Crypto from "effect/Crypto";
@@ -98,6 +99,44 @@ export function isAgentActivityPublishingEnabled(value: string | null): boolean · 74 unmodified lines
98 return value === "true";99 return value === "true";
99}100}
100 101
102export function relayUrlLogAttributes(relayUrl: string | undefined) {
103 if (relayUrl === undefined) {
104 return { relayUrlConfigured: false };
105 }
106 const diagnostics = getUrlDiagnostics(relayUrl);
107 return {
108 relayUrlConfigured: true,
109 relayUrlInputLength: diagnostics.inputLength,
110 ...(diagnostics.protocol === undefined ? {} : { relayUrlProtocol: diagnostics.protocol }),
111 ...(diagnostics.hostname === undefined ? {} : { relayUrlHostname: diagnostics.hostname }),
112 };
113}
114
115export function relayPublishCauseLogAttributes(cause: Cause.Cause<unknown>) {
116 const failureTags = cause.reasons.flatMap((reason) => {
117 if (!Cause.isFailReason(reason)) {
118 return [];
119 }
120 const error = reason.error;
121 if (
122 typeof error !== "object" ||
123 error === null ||
124 !("_tag" in error) ||
125 typeof error._tag !== "string"
126 ) {
127 return [];
128 }
129 return [error._tag];
130 });
131 return {
132 causeReasonCount: cause.reasons.length,
133 causeFailureCount: cause.reasons.filter(Cause.isFailReason).length,
134 causeDefectCount: cause.reasons.filter(Cause.isDieReason).length,
135 causeInterruptionCount: cause.reasons.filter(Cause.isInterruptReason).length,
136 causeFailureTags: [...new Set(failureTags)],
137 };
138}
139
101export function resolveAgentActivityPublishingStartupState(input: {140export function resolveAgentActivityPublishingStartupState(input: {
102 readonly relayConfigured: boolean;141 readonly relayConfigured: boolean;
103 readonly publishEnabled: boolean;142 readonly publishEnabled: boolean;
@@ -417,12 +456,12 @@ export const make = Effect.gen(function* () { · 313 unmodified lines
417 456
418 const publishThread: AgentAwarenessRelay["Service"]["publishThread"] = (threadId) =>457 const publishThread: AgentAwarenessRelay["Service"]["publishThread"] = (threadId) =>
419 publishThreadUnsafe(threadId).pipe(458 publishThreadUnsafe(threadId).pipe(
420 Effect.catchCause((cause) => {459 Effect.catchCause((cause) =>
421 return Effect.logWarning("agent activity publish failed", {460 Effect.logWarning("agent activity publish failed", {
422 threadId,461 threadId,
423 cause: Cause.pretty(cause),462 ...relayPublishCauseLogAttributes(cause),
424 });463 }),
425 }),464 ),
426 Effect.withSpan("AgentAwarenessRelay.publishThread"),465 Effect.withSpan("AgentAwarenessRelay.publishThread"),
427 withRelayClientTracing,466 withRelayClientTracing,
428 );467 );
@@ -467,7 +506,7 @@ export const make = Effect.gen(function* () { · 38 unmodified lines
467 if (logEnabledWhenReady) {506 if (logEnabledWhenReady) {
468 const relayConfig = yield* readRelayConfig.pipe(Effect.orElseSucceed(() => null));507 const relayConfig = yield* readRelayConfig.pipe(Effect.orElseSucceed(() => null));
469 yield* Effect.logInfo("agent activity publishing enabled after link reconciliation", {508 yield* Effect.logInfo("agent activity publishing enabled after link reconciliation", {
470 relayUrl: relayConfig?.url,509 ...relayUrlLogAttributes(relayConfig?.url),
471 });510 });
472 }511 }
473 return;512 return;
@@ -499,7 +538,7 @@ export const make = Effect.gen(function* () { · 25 unmodified lines
499 break;538 break;
500 case "enabled":539 case "enabled":
501 yield* Effect.logInfo("agent activity publishing enabled", {540 yield* Effect.logInfo("agent activity publishing enabled", {
502 relayUrl: relayConfig?.url,541 ...relayUrlLogAttributes(relayConfig?.url),
503 });542 });
504 break;543 break;
505 }544 }