Skip to content

Commit 5f817e6

Browse files
authored
[codex] Merge artifact telemetry into content consent (#4087)
* Merge artifact telemetry into content consent * Localize privacy telemetry disclosure copy Generated-By: looper 0.9.7 (runner=fixer, agent=codex)
1 parent 0e592c3 commit 5f817e6

31 files changed

Lines changed: 89 additions & 150 deletions

apps/daemon/src/app-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ function applyTelemetryDefaults(prefs: AppConfigPrefs): AppConfigPrefs {
461461
if (prefs.telemetry === undefined) {
462462
return {
463463
...prefs,
464-
telemetry: { metrics: true, content: true, artifactManifest: false },
464+
telemetry: { metrics: true, content: true },
465465
};
466466
}
467467
return prefs;

apps/daemon/src/langfuse-trace.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
// Privacy gates are layered: `prefs.metrics` is the master switch, and
1111
// `prefs.content` is required for Langfuse traces because this sink is used
1212
// for turn-quality evals. If either is off, no network call is made.
13-
// `prefs.artifactManifest` decides whether the produced-files manifest is
14-
// included. None of these defaults to true; the Web onboarding flow flips
15-
// metrics + content after explicit consent.
13+
// Complete-context manifests are part of content telemetry: when metrics and
14+
// content are both enabled, Langfuse receives the trace and associated object
15+
// references. If either is off, no network call is made.
1616
//
1717
// See: specs/change/20260507-langfuse-telemetry/spec.md
1818

@@ -923,14 +923,15 @@ function buildTimingSpanBodies(
923923
end: runEnd,
924924
input: {
925925
phase: 'finalize',
926-
artifact_manifest_enabled: ctx.prefs.artifactManifest === true,
926+
artifact_manifest_enabled: ctx.prefs.metrics === true && ctx.prefs.content === true,
927927
},
928928
output: {
929929
status: ctx.run.status,
930930
artifact_count: ctx.artifacts.length,
931931
attachment_count: ctx.attachmentManifest?.length ?? 0,
932932
manifest_completeness:
933-
ctx.manifestCompleteness ?? (ctx.prefs.artifactManifest ? 'unavailable' : 'off'),
933+
ctx.manifestCompleteness ??
934+
(ctx.prefs.metrics === true && ctx.prefs.content === true ? 'unavailable' : 'off'),
934935
},
935936
metadata: { boundary: 'finalizeStartAt -> run.endedAt' },
936937
},
@@ -1013,7 +1014,7 @@ function shouldCreateGenerationObservation(ctx: ReportContext): boolean {
10131014

10141015
export function buildTracePayload(ctx: ReportContext): unknown[] {
10151016
const wantsContent = ctx.prefs.metrics === true && ctx.prefs.content === true;
1016-
const wantsArtifacts = ctx.prefs.artifactManifest === true;
1017+
const wantsArtifacts = wantsContent;
10171018

10181019
const sessionId =
10191020
ctx.conversationId.length <= SESSION_ID_MAX ? ctx.conversationId : undefined;

apps/daemon/src/trace-object-manifest.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,8 +643,7 @@ export async function buildTraceObjectManifests(
643643
): Promise<TraceObjectUploadManifests | undefined> {
644644
if (
645645
opts.prefs.metrics !== true ||
646-
opts.prefs.content !== true ||
647-
opts.prefs.artifactManifest !== true
646+
opts.prefs.content !== true
648647
) {
649648
return undefined;
650649
}

apps/daemon/tests/app-config.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { isLocalSameOrigin } from '../src/origin-validation.js';
2525
const DEFAULT_TELEMETRY = {
2626
metrics: true,
2727
content: true,
28-
artifactManifest: false,
2928
} as const;
3029

3130
describe('app-config', () => {

apps/daemon/tests/langfuse-bridge.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,14 +1451,10 @@ describe('langfuse-bridge.reportRunCompletedFromDaemon', () => {
14511451
expect(generation.model).toBe('default');
14521452
});
14531453

1454-
it('omits artifacts when that gate is off', async () => {
1454+
it('includes artifacts when content telemetry is on', async () => {
14551455
await writeAppCfg({
14561456
installationId: 'install-1',
1457-
telemetry: {
1458-
metrics: true,
1459-
content: true,
1460-
artifactManifest: false,
1461-
},
1457+
telemetry: { metrics: true, content: true },
14621458
});
14631459
const messages: FakeMessage[] = [
14641460
{
@@ -1488,7 +1484,9 @@ describe('langfuse-bridge.reportRunCompletedFromDaemon', () => {
14881484
const trace = JSON.parse(init.body as string).batch[0].body;
14891485
expect(trace.input).toBe('design a coffee landing page');
14901486
expect(trace.output).toBe('sensitive output');
1491-
expect(trace.metadata.artifacts).toBeUndefined();
1487+
expect(trace.metadata.artifacts).toEqual([
1488+
{ slug: 'secret.html', type: 'html', sizeBytes: 1 },
1489+
]);
14921490
// tokens + eventsSummary are still in metadata since they're metrics
14931491
expect(trace.metadata.tokens).toEqual({
14941492
input: 100,

apps/daemon/tests/langfuse-trace.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -494,10 +494,10 @@ describe('buildTracePayload', () => {
494494
expect(trace.metadata.manifest_completeness).toBeUndefined();
495495
});
496496

497-
it('includes trace-safe object manifests when the artifact manifest gate is on', () => {
497+
it('includes trace-safe object manifests when content telemetry is on', () => {
498498
const batch = buildTracePayload(
499499
makeCtx({
500-
prefs: { metrics: true, content: false, artifactManifest: true },
500+
prefs: { metrics: true, content: true },
501501
attachmentManifest: [
502502
{
503503
attachment_id: 'att-1',
@@ -589,7 +589,7 @@ describe('buildTracePayload', () => {
589589
const batch = buildTracePayload(
590590
makeCtx({
591591
artifacts: many,
592-
prefs: { metrics: true, content: false, artifactManifest: true },
592+
prefs: { metrics: true, content: true },
593593
}),
594594
);
595595
const trace = (batch[0] as any).body;
@@ -622,7 +622,7 @@ describe('buildTracePayload', () => {
622622
makeCtx({
623623
artifactManifest: many,
624624
manifestCompleteness: 'complete',
625-
prefs: { metrics: true, content: false, artifactManifest: true },
625+
prefs: { metrics: true, content: true },
626626
}),
627627
);
628628
const trace = (batch[0] as any).body;
@@ -657,7 +657,7 @@ describe('buildTracePayload', () => {
657657
makeCtx({
658658
attachmentManifest: many,
659659
manifestCompleteness: 'complete',
660-
prefs: { metrics: true, content: false, artifactManifest: true },
660+
prefs: { metrics: true, content: true },
661661
run: {
662662
runId: 'run-1',
663663
status: 'succeeded',
@@ -1443,7 +1443,7 @@ describe('reportRunCompleted', () => {
14431443
const fetchSpy = vi.fn();
14441444
const result = await reportRunCompleted(
14451445
makeCtx({
1446-
prefs: { metrics: true, content: false, artifactManifest: true },
1446+
prefs: { metrics: true, content: false },
14471447
}),
14481448
{ config: TEST_CONFIG, fetchImpl: fetchSpy as any },
14491449
);

apps/web/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2202,7 +2202,7 @@ function AppInner() {
22022202
...latestPersistedConfigRef.current,
22032203
installationId,
22042204
privacyDecisionAt: Date.now(),
2205-
telemetry: { metrics: true, content: true, artifactManifest: false },
2205+
telemetry: { metrics: true, content: true },
22062206
});
22072207
}}
22082208
/>

apps/web/src/components/PrivacySection.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
4949
...c,
5050
installationId: generateInstallationId(),
5151
privacyDecisionAt: Date.now(),
52-
telemetry: { metrics: true, content: true, artifactManifest: false },
52+
telemetry: { metrics: true, content: true },
5353
}));
5454
}
5555

@@ -58,7 +58,7 @@ export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
5858
...c,
5959
installationId: null,
6060
privacyDecisionAt: Date.now(),
61-
telemetry: { metrics: false, content: false, artifactManifest: false },
61+
telemetry: { metrics: false, content: false },
6262
}));
6363
}
6464

@@ -67,7 +67,7 @@ export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
6767
...c,
6868
installationId: generateInstallationId(),
6969
privacyDecisionAt: c.privacyDecisionAt ?? Date.now(),
70-
telemetry: { metrics: false, content: false, artifactManifest: false },
70+
telemetry: { metrics: false, content: false },
7171
}));
7272
}
7373

@@ -106,20 +106,6 @@ export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
106106
patchTelemetry({ content: v });
107107
}}
108108
/>
109-
<ToggleRow
110-
label={t('settings.privacyArtifacts')}
111-
hint={t('settings.privacyArtifactsHint')}
112-
checked={telemetry.artifactManifest === true}
113-
onChange={(v) => {
114-
trackSettingsPrivacyClick(analytics.track, {
115-
page_name: 'settings',
116-
area: 'privacy',
117-
element: 'project_artifacts_manifest',
118-
project_artifacts_manifest_status: v ? 'on' : 'off',
119-
});
120-
patchTelemetry({ artifactManifest: v });
121-
}}
122-
/>
123109
</div>
124110

125111
<div className="settings-subsection">

apps/web/src/i18n/locales/ar.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,18 +369,16 @@ export const ar: Dict = {
369369
'settings.privacyHint': 'البيانات التي تتم مشاركتها مع فريق Open Design',
370370
'settings.privacyConsentKicker': 'ساعدنا في تحسين Open Design',
371371
'settings.privacyConsentLead': 'يمكن لـ Open Design مشاركة بيانات الاستخدام مع فريقنا لمساعدتنا على التحسين. يشمل ذلك:',
372-
'settings.privacyConsentFooter': 'يمكنك تغيير أي من هذه في أي وقت من الإعدادات ← الخصوصية. نحن لا نرفع محتويات ملفات المخرجات التي تنشئها أبدًا.',
372+
'settings.privacyConsentFooter': 'يمكنك تغيير هذه الخيارات في أي وقت من الإعدادات ← الخصوصية.',
373373
'settings.privacyConsentShare': 'مشاركة بيانات الاستخدام',
374374
'settings.privacyConsentDecline': 'عدم المشاركة',
375375
'settings.privacyConsentAccept': 'فهمت',
376-
'settings.privacyConsentBannerFooter': 'مشاركة البيانات مفعّلة افتراضيًا. يمكنك إيقافها في أي وقت من الإعدادات ← الخصوصية. نحن لا نرفع محتويات ملفات المخرجات التي تنشئها أبدًا.',
376+
'settings.privacyConsentBannerFooter': 'مشاركة البيانات مفعّلة افتراضيًا. يمكنك إيقافها في أي وقت من الإعدادات ← الخصوصية.',
377377
'settings.privacyConsentPolicyLink': 'اقرأ سياسة الخصوصية',
378378
'settings.privacyMetrics': 'مقاييس مجهولة المصدر',
379379
'settings.privacyMetricsHint': 'عدد مرات التشغيل، واستخدام Token، ومعدل الأخطاء، والمدة. بدون أي مطالبات، وبدون أي بيانات للمشروع.',
380380
'settings.privacyContent': 'محتوى المحادثة والأدوات',
381-
'settings.privacyContentHint': 'مطالباتك، وردود المساعد، ومدخلات الأدوات، ومخرجات الأدوات (تُقتطع قبل الإرسال). تتم إزالة مفاتيح API والرموز (Token) وJWT والبريد الإلكتروني وعناوين IP وأرقام بطاقات الائتمان تلقائيًا قبل الإرسال.',
382-
'settings.privacyArtifacts': 'بيان أصول المشروع',
383-
'settings.privacyArtifactsHint': 'أسماء الملفات المُنشأة وأنواعها وأحجامها. لا يتم إرسال محتويات الملفات أبدًا.',
381+
'settings.privacyContentHint': 'المطالبات، وردود المساعد، ومدخلات/مخرجات الأدوات، وملفات السياق الكامل اللازمة لمراجعة الجودة. تتم إزالة الأسرار قبل الإرسال.',
384382
'settings.privacyInstallationId': 'المُعرّف المجهول',
385383
'settings.privacyOptedOut': 'تم إلغاء الاشتراك',
386384
'settings.privacyDataDeletion': 'حذف بياناتي',

apps/web/src/i18n/locales/de.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,18 +369,16 @@ export const de: Dict = {
369369
'settings.privacyHint': 'Welche Daten mit dem Open Design-Team geteilt werden',
370370
'settings.privacyConsentKicker': 'Hilf uns, Open Design zu verbessern',
371371
'settings.privacyConsentLead': 'Open Design kann Nutzungsdaten mit unserem Team teilen, um uns bei Verbesserungen zu helfen. Dazu gehören:',
372-
'settings.privacyConsentFooter': 'Du kannst beides jederzeit unter Einstellungen → Datenschutz ändern. Wir laden niemals die Inhalte deiner generierten Artefaktdateien hoch.',
372+
'settings.privacyConsentFooter': 'Du kannst diese Einstellungen jederzeit unter Einstellungen → Datenschutz ändern.',
373373
'settings.privacyConsentShare': 'Nutzungsdaten teilen',
374374
'settings.privacyConsentDecline': 'Nicht teilen',
375375
'settings.privacyConsentAccept': 'Verstanden',
376-
'settings.privacyConsentBannerFooter': 'Datenfreigabe ist standardmäßig aktiviert. Du kannst sie jederzeit unter Einstellungen → Datenschutz deaktivieren. Wir laden niemals die Inhalte deiner generierten Artefaktdateien hoch.',
376+
'settings.privacyConsentBannerFooter': 'Datenfreigabe ist standardmäßig aktiviert. Du kannst sie jederzeit unter Einstellungen → Datenschutz deaktivieren.',
377377
'settings.privacyConsentPolicyLink': 'Datenschutzrichtlinie lesen',
378378
'settings.privacyMetrics': 'Anonyme Metriken',
379379
'settings.privacyMetricsHint': 'Ausführungsanzahl, Token-Verbrauch, Fehlerrate, Dauer. Keine Prompts, keine Projektdaten.',
380380
'settings.privacyContent': 'Conversation content',
381-
'settings.privacyContentHint': 'Your prompts and the assistant\'s responses (truncated 8 KB / 16 KB). API keys, tokens, JWTs, emails, IPs, and credit-card numbers are stripped automatically before send.',
382-
'settings.privacyArtifacts': 'Manifest der Projekt-Artefakte',
383-
'settings.privacyArtifactsHint': 'Dateinamen, -typen und -größen der generierten Dateien. Dateiinhalte werden niemals gesendet.',
381+
'settings.privacyContentHint': 'Prompts, Antworten des Assistenten, Ein- und Ausgaben von Tools sowie vollständige Kontextdateien, die für die Qualitätsprüfung benötigt werden. Geheimnisse werden vor dem Senden entfernt.',
384382
'settings.privacyInstallationId': 'Anonyme ID',
385383
'settings.privacyOptedOut': 'abgemeldet',
386384
'settings.privacyDataDeletion': 'Meine Daten löschen',

0 commit comments

Comments
 (0)