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
37 changes: 37 additions & 0 deletions console/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,43 @@ export interface Run {
permission_mode?: 'approval' | '';
/** When the run entered awaiting_input (idle-timeout epoch). */
awaiting_since?: string | null;
/**
* Read-only identity/source projection frozen at Run creation. These display
* references are audit metadata only and must never be used for authorization.
*/
provenance?: RunProvenance;
}

export interface ProvenanceActorRef {
kind: 'cloud_user' | 'external_actor' | 'service_principal' | 'automation_principal' | 'provider_bot' | string;
id?: string;
label: string;
provider?: string;
external_id?: string;
external_label?: string;
}

export interface RunProvenance {
requested_actor?: ProvenanceActorRef;
accountable_actor?: ProvenanceActorRef;
attribution_source: string;
precision: 'exact' | 'linked_external' | 'rule_owner' | 'unattributed' | string;
trigger: {
kind: string;
label: string;
ref?: string;
href?: string;
};
executed_for: {
project_id: string;
project_label: string;
service_id: string;
service_label: string;
repository?: string;
model?: string;
};
runtime_principal: ProvenanceActorRef;
writeback_actor?: ProvenanceActorRef;
}

/* ---- session permission approval (F8b / D22) ------------------------------ */
Expand Down
22 changes: 22 additions & 0 deletions console/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,28 @@ export default {
},
inspector: {
runDetailsLabel: 'Run details',
identityAndSource: 'Identity & source',
requestedBy: 'Requested by',
accountableTo: 'Accountable to',
triggeredFrom: 'Triggered from',
executedFor: 'Executed for',
project: 'Project',
repository: 'Repository',
dispatchSnapshot: 'Dispatch-time snapshot',
writtenBackAs: 'Written back as',
externalActor: 'External actor',
ruleOwner: 'Rule owner',
botOrApp: 'Bot / App',
notAttributed: 'Not attributed',
notApplicable: 'Not applicable',
technicalIdentity: 'Technical identity',
runtimePrincipal: 'Runtime',
attribution: 'Attribution',
source: 'Source',
precisionExact: 'Exact',
precisionLinkedExternal: 'Linked external identity',
precisionRuleOwner: 'Attributed to rule owner',
precisionUnattributed: 'Unattributed',
runOverview: 'Run overview',
service: 'Service',
unavailable: 'Unavailable',
Expand Down
22 changes: 22 additions & 0 deletions console/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,28 @@ export default {
},
inspector: {
runDetailsLabel: '実行の詳細',
identityAndSource: '依頼者と実行元',
requestedBy: 'リクエスト元',
accountableTo: '責任者',
triggeredFrom: 'トリガー元',
executedFor: '実行対象',
project: 'プロジェクト',
repository: 'リポジトリ',
dispatchSnapshot: 'ディスパッチ時のスナップショット',
writtenBackAs: '書き戻し ID',
externalActor: '外部 ID',
ruleOwner: 'ルール所有者',
botOrApp: 'Bot / アプリ',
notAttributed: '帰属なし',
notApplicable: '該当なし',
technicalIdentity: '技術的な実行情報',
runtimePrincipal: 'ランタイム',
attribution: '帰属',
source: 'ソース',
precisionExact: '正確',
precisionLinkedExternal: '外部 ID にリンク',
precisionRuleOwner: 'ルール所有者に帰属',
precisionUnattributed: '帰属なし',
runOverview: '実行の概要',
service: 'サービス',
unavailable: '利用不可',
Expand Down
22 changes: 22 additions & 0 deletions console/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,28 @@ export default {
},
inspector: {
runDetailsLabel: '실행 세부 정보',
identityAndSource: '요청자 및 실행 출처',
requestedBy: '요청자',
accountableTo: '책임자',
triggeredFrom: '트리거 소스',
executedFor: '실행 대상',
project: '프로젝트',
repository: '저장소',
dispatchSnapshot: '디스패치 시점 스냅샷',
writtenBackAs: '기록 ID',
externalActor: '외부 ID',
ruleOwner: '규칙 소유자',
botOrApp: 'Bot / 앱',
notAttributed: '귀속되지 않음',
notApplicable: '해당 없음',
technicalIdentity: '기술 실행 정보',
runtimePrincipal: '런타임',
attribution: '귀속',
source: '소스',
precisionExact: '정확',
precisionLinkedExternal: '외부 ID 연결',
precisionRuleOwner: '규칙 소유자에게 귀속',
precisionUnattributed: '귀속되지 않음',
runOverview: '실행 개요',
service: '서비스',
unavailable: '사용 불가',
Expand Down
22 changes: 22 additions & 0 deletions console/src/i18n/locales/zh-Hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1800,6 +1800,28 @@ export default {
},
inspector: {
runDetailsLabel: '运行详情',
identityAndSource: '身份与来源',
requestedBy: '请求者',
accountableTo: '责任人',
triggeredFrom: '触发来源',
executedFor: '执行对象',
project: '项目',
repository: '代码仓库',
dispatchSnapshot: '调度时快照',
writtenBackAs: '回写身份',
externalActor: '外部身份',
ruleOwner: '规则负责人',
botOrApp: 'Bot / 应用',
notAttributed: '无法归属',
notApplicable: '不适用',
technicalIdentity: '技术身份',
runtimePrincipal: '运行主体',
attribution: '归属精度',
source: '来源',
precisionExact: '精确',
precisionLinkedExternal: '已关联外部身份',
precisionRuleOwner: '归属到规则负责人',
precisionUnattributed: '无法归属',
runOverview: '运行概览',
service: '服务',
unavailable: '不可用',
Expand Down
22 changes: 22 additions & 0 deletions console/src/i18n/locales/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,28 @@ export default {
},
inspector: {
runDetailsLabel: '執行詳情',
identityAndSource: '身分與來源',
requestedBy: '請求者',
accountableTo: '責任人',
triggeredFrom: '觸發來源',
executedFor: '執行對象',
project: '專案',
repository: '程式碼倉庫',
dispatchSnapshot: '調度時快照',
writtenBackAs: '回寫身分',
externalActor: '外部身分',
ruleOwner: '規則負責人',
botOrApp: 'Bot / 應用程式',
notAttributed: '無法歸屬',
notApplicable: '不適用',
technicalIdentity: '技術身分',
runtimePrincipal: '執行主體',
attribution: '歸屬精度',
source: '來源',
precisionExact: '精確',
precisionLinkedExternal: '已連結外部身分',
precisionRuleOwner: '歸屬到規則負責人',
precisionUnattributed: '無法歸屬',
runOverview: '執行總覽',
service: '服務',
unavailable: '無法使用',
Expand Down
13 changes: 12 additions & 1 deletion console/src/pages/RunDetailPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@
.facts dt { color: var(--color-text-faint); font-size: var(--fs-tiny); }
.facts dd { min-width: 0; margin: 0; color: var(--color-text-muted); font-size: var(--fs-tiny); overflow-wrap: anywhere; }
.facts code { font-family: var(--font-mono); }
.provenanceFacts > div { grid-template-columns: 88px minmax(0, 1fr); }
.actorDisplay, .executedFor, .triggerReference { display: grid; gap: 2px; }
.actorDisplay small, .executedFor small, .triggerReference small { color: var(--color-text-faint); font-size: 10px; }
.botIdentity { display: flex; align-items: flex-start; gap: var(--space-2); }
.botMark { display: grid; width: 30px; height: 30px; flex: none; place-items: center; border-radius: var(--radius-md); background: color-mix(in srgb, var(--color-accent) 12%, transparent); color: var(--color-accent); font-family: var(--font-mono); font-size: 10px; font-weight: var(--fw-semibold); }
.botIdentity .actorDisplay > span { color: var(--color-text); font-weight: var(--fw-medium); }
.provenanceTechnical { color: var(--color-text-faint); font-size: var(--fs-tiny); }
.provenanceTechnical summary { width: max-content; cursor: pointer; }
.provenanceTechnical[open] summary { margin-bottom: var(--space-3); }
.provenanceTechnical .facts { padding-left: var(--space-2); border-left: 1px solid var(--color-border); }
.provenanceTechnical a, .provenanceFacts a { display: inline-flex; align-items: center; gap: 3px; color: var(--color-text-muted); }
.inspectorHint { margin: 0; color: var(--color-text-faint); font-size: var(--fs-tiny); line-height: var(--lh-normal); }
.changeList { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2); color: var(--color-text-faint); font-size: var(--fs-tiny); }
.changeList code { overflow: hidden; color: var(--color-text-muted); font-family: var(--font-mono); text-overflow: ellipsis; white-space: nowrap; }
Expand Down Expand Up @@ -146,7 +157,7 @@
@media (max-width: 940px) {
.taskLayout { grid-template-columns: minmax(0, 1fr); align-content: start; overflow-y: auto; }
.conversationColumn { min-height: 560px; }
.inspector { grid-template-columns: repeat(3, minmax(0, 1fr)); padding-top: var(--space-5); border-top: 1px solid var(--color-border); border-left: 0; overflow: visible; }
.inspector { grid-template-columns: repeat(2, minmax(0, 1fr)); padding-top: var(--space-5); border-top: 1px solid var(--color-border); border-left: 0; overflow: visible; }
.inspectorSection { padding: var(--space-5) var(--space-4) 0 0; }
.inspectorSection + .inspectorSection { padding-top: var(--space-5); padding-left: var(--space-4); border-top: 0; border-left: 1px solid var(--color-border); }
}
Expand Down
74 changes: 74 additions & 0 deletions console/src/pages/RunDetailPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,80 @@ describe('RunDetailPage — resilient error states', () => {
expect(screen.queryByTestId('tab-events')).toBeNull();
});

it('separates requester, rule owner, execution target, and Bot writeback identity', async () => {
const { client, ctl } = makeClient('member');
const run = baseRun({
service_id: 'svc-1',
origin: 'kanban',
provenance: {
requested_actor: {
kind: 'external_actor',
label: 'Mei',
provider: 'jtype',
},
accountable_actor: {
kind: 'cloud_user',
id: 'user-jack',
label: 'Jack',
},
attribution_source: 'kanban_event',
precision: 'rule_owner',
trigger: {
kind: 'kanban_card',
label: 'JType Card #42',
ref: 'occurrence-42',
},
executed_for: {
project_id: 'proj1',
project_label: 'commerce',
service_id: 'svc-1',
service_label: 'payments-api',
repository: 'acme/payments',
model: 'anthropic/claude-sonnet',
},
runtime_principal: {
kind: 'automation_principal',
label: 'Cloud Automation',
},
writeback_actor: {
kind: 'provider_bot',
label: 'jcode Cloud Bot',
provider: 'jtype',
},
},
});
ctl.getRun.mockResolvedValue(run);
renderPage(client, run);

expect((await screen.findByTestId('run-inspector')).textContent).toContain('Identity & source');
expect(screen.getByTestId('provenance-requested').textContent).toContain('Mei');
expect(screen.getByTestId('provenance-requested').textContent).toContain('External actor');
expect(screen.getByTestId('provenance-requested').textContent).not.toContain('Bot');
expect(screen.getByTestId('provenance-accountable').textContent).toContain('Jack');
expect(screen.getByTestId('provenance-accountable').textContent).toContain('Rule owner');
expect(screen.getByTestId('provenance-trigger').textContent).toContain('JType Card #42');
expect(screen.getByTestId('provenance-executed-for').textContent).toContain('commerce');
expect(screen.getByTestId('provenance-executed-for').textContent).toContain('payments-api');
expect(screen.getByTestId('provenance-executed-for').textContent).toContain('acme/payments');
expect(screen.getByTestId('provenance-executed-for').textContent).toContain('anthropic/claude-sonnet');
expect(screen.getByTestId('provenance-executed-for').textContent).toContain('Dispatch-time snapshot');
expect(screen.getByTestId('provenance-written-back').textContent).toContain('jcode Cloud Bot');
expect(screen.getByTestId('provenance-written-back').textContent).toContain('Bot / App');
});

it('labels missing legacy provenance without inventing a person, Bot, or zero value', async () => {
const { client, ctl } = makeClient('viewer');
const run = baseRun();
ctl.getRun.mockResolvedValue(run);
renderPage(client, run);

expect((await screen.findByTestId('provenance-requested')).textContent).toBe('Not attributed');
expect(screen.getByTestId('provenance-accountable').textContent).toBe('Not attributed');
expect(screen.getByTestId('provenance-trigger').textContent).toBe('Unavailable');
expect(screen.getByTestId('provenance-executed-for').textContent).toContain('Unavailable');
expect(screen.getByTestId('provenance-written-back').textContent).toBe('Not applicable');
});

it('truncates a long task title with the complete prompt available on hover', async () => {
const prompt = '# A deliberately long task title\n\n## Details\n' + 'keep this context '.repeat(40);
const { client, ctl } = makeClient('member');
Expand Down
Loading
Loading