@company-semantics/contracts 0.25.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/guards/config.ts +110 -1
- package/src/guards/index.ts +6 -0
- package/src/index.ts +35 -0
- package/src/message-parts/__tests__/confirmation.test.ts +99 -0
- package/src/message-parts/__tests__/preview.test.ts +97 -0
- package/src/message-parts/confirmation.ts +87 -0
- package/src/message-parts/execution.ts +53 -0
- package/src/message-parts/index.ts +29 -1
- package/src/message-parts/preview.ts +81 -0
- package/src/message-parts/types.ts +7 -14
- package/src/message-parts/wire.ts +82 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
"guard:scripts-deps": "npx tsx scripts/ci/scripts-deps-guard.ts",
|
|
54
54
|
"guard:version-tag": "npx tsx scripts/ci/version-tag-guard.ts",
|
|
55
55
|
"guard:version-tag:json": "npx tsx scripts/ci/version-tag-guard.ts --json",
|
|
56
|
+
"guard:decisions-deprecation": "npx tsx scripts/ci/decisions-deprecation-guard.ts",
|
|
57
|
+
"guard:decisions-deprecation:json": "npx tsx scripts/ci/decisions-deprecation-guard.ts --json",
|
|
56
58
|
"guard:test": "vitest run scripts/ci/__tests__",
|
|
57
59
|
"release": "npx tsx scripts/release.ts",
|
|
58
60
|
"prepublishOnly": "echo 'ERROR: Publishing is CI-only via tag push. Use pnpm release instead.' && exit 1",
|
package/src/guards/config.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Each repo provides its own config values; shared guards consume them.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { CheckResult } from './types.js';
|
|
11
|
+
import type { CheckResult, Soc2ControlArea } from './types.js';
|
|
12
12
|
|
|
13
13
|
// =============================================================================
|
|
14
14
|
// Size Limits
|
|
@@ -376,6 +376,115 @@ export interface SubdirectoryAffinityBaseline {
|
|
|
376
376
|
ignoredDirectories?: string[];
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
// =============================================================================
|
|
380
|
+
// SOC 2 Guard Configuration Types
|
|
381
|
+
// =============================================================================
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Configuration for the secrets detection guard.
|
|
385
|
+
* This is a blocking control for SOC 2 Access Control (AC).
|
|
386
|
+
*/
|
|
387
|
+
export interface SecretsDetectionConfig {
|
|
388
|
+
/** Source directory to scan (relative to repo root) */
|
|
389
|
+
srcDir?: string;
|
|
390
|
+
/** Additional directories to scan */
|
|
391
|
+
additionalDirs?: string[];
|
|
392
|
+
/** File patterns to exclude (globs) */
|
|
393
|
+
excludePatterns?: string[];
|
|
394
|
+
/** Additional secret patterns to detect (regex strings) */
|
|
395
|
+
additionalPatterns?: string[];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Configuration for the structured logging guard.
|
|
400
|
+
* This is a non-blocking control for SOC 2 Logging & Monitoring (LM).
|
|
401
|
+
*/
|
|
402
|
+
export interface StructuredLoggingConfig {
|
|
403
|
+
/** Entry point files to check (relative to repo root) */
|
|
404
|
+
entryPoints?: string[];
|
|
405
|
+
/** Source directory to scan for logging (fallback if no entry points) */
|
|
406
|
+
srcDir?: string;
|
|
407
|
+
/** Recognized logging libraries */
|
|
408
|
+
loggingLibraries?: string[];
|
|
409
|
+
/** Logger instantiation patterns (regex strings) */
|
|
410
|
+
instantiationPatterns?: string[];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Configuration for the alerts config guard.
|
|
415
|
+
* This is a non-blocking control for SOC 2 Logging & Monitoring (LM).
|
|
416
|
+
*/
|
|
417
|
+
export interface AlertsConfigGuardConfig {
|
|
418
|
+
/**
|
|
419
|
+
* Files or directories to check for alerting configuration.
|
|
420
|
+
* Supports both file paths and directory paths.
|
|
421
|
+
*/
|
|
422
|
+
files?: string[];
|
|
423
|
+
/**
|
|
424
|
+
* Whether this check should be skipped.
|
|
425
|
+
* Use for repos that don't have alerting (e.g., pure libraries).
|
|
426
|
+
*/
|
|
427
|
+
skip?: boolean;
|
|
428
|
+
/**
|
|
429
|
+
* Evidence string when alerting is explicitly skipped.
|
|
430
|
+
*/
|
|
431
|
+
skipEvidence?: string;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Configuration for the backup config guard.
|
|
436
|
+
* This is a non-blocking control for SOC 2 Backup & Recovery (BR).
|
|
437
|
+
*/
|
|
438
|
+
export interface BackupConfigGuardConfig {
|
|
439
|
+
/**
|
|
440
|
+
* Files or directories to check for backup configuration.
|
|
441
|
+
*/
|
|
442
|
+
files?: string[];
|
|
443
|
+
/**
|
|
444
|
+
* Whether this check should be skipped.
|
|
445
|
+
* Use for repos that don't manage backups (e.g., frontend-only).
|
|
446
|
+
*/
|
|
447
|
+
skip?: boolean;
|
|
448
|
+
/**
|
|
449
|
+
* Evidence string when backup check is explicitly skipped.
|
|
450
|
+
*/
|
|
451
|
+
skipEvidence?: string;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* SOC 2 compliance baselines.
|
|
456
|
+
* Configures SOC 2 control guards for a repository.
|
|
457
|
+
*
|
|
458
|
+
* Design: Product repos provide data only; CI orchestrator owns guard implementations.
|
|
459
|
+
*/
|
|
460
|
+
export interface Soc2Baselines {
|
|
461
|
+
/** Enable SOC 2 compliance reporting */
|
|
462
|
+
enabled?: boolean;
|
|
463
|
+
/** Advisory mode: all controls become non-blocking for visibility-only rollout */
|
|
464
|
+
advisoryMode?: boolean;
|
|
465
|
+
/** Repository name (for evidence) */
|
|
466
|
+
repository?: string;
|
|
467
|
+
/** Secrets detection configuration */
|
|
468
|
+
secretsDetection?: SecretsDetectionConfig;
|
|
469
|
+
/** Structured logging configuration */
|
|
470
|
+
structuredLogging?: StructuredLoggingConfig;
|
|
471
|
+
/** Alerts config configuration */
|
|
472
|
+
alertsConfig?: AlertsConfigGuardConfig;
|
|
473
|
+
/** Backup config configuration */
|
|
474
|
+
backupConfig?: BackupConfigGuardConfig;
|
|
475
|
+
/** Controls to explicitly skip (with evidence) */
|
|
476
|
+
skipControls?: {
|
|
477
|
+
area: Soc2ControlArea;
|
|
478
|
+
evidence: string;
|
|
479
|
+
}[];
|
|
480
|
+
/**
|
|
481
|
+
* Controls that remain blocking even in advisory mode.
|
|
482
|
+
* Use to gradually promote controls from advisory to blocking.
|
|
483
|
+
* Example: ['AC'] to make secrets-detection blocking first.
|
|
484
|
+
*/
|
|
485
|
+
alwaysBlockingControls?: Soc2ControlArea[];
|
|
486
|
+
}
|
|
487
|
+
|
|
379
488
|
/**
|
|
380
489
|
* Registry of checks grouped by tier.
|
|
381
490
|
* Each repo exports this from guard-entries.ts for universal orchestration.
|
package/src/guards/index.ts
CHANGED
|
@@ -54,6 +54,12 @@ export type {
|
|
|
54
54
|
FileClusterBaseline,
|
|
55
55
|
SubdirectoryAffinityBaseline,
|
|
56
56
|
MetaBaselines,
|
|
57
|
+
// SOC 2 Guard Configuration types
|
|
58
|
+
SecretsDetectionConfig,
|
|
59
|
+
StructuredLoggingConfig,
|
|
60
|
+
AlertsConfigGuardConfig,
|
|
61
|
+
BackupConfigGuardConfig,
|
|
62
|
+
Soc2Baselines,
|
|
57
63
|
} from './config.js';
|
|
58
64
|
|
|
59
65
|
// Config constants (type-level defaults)
|
package/src/index.ts
CHANGED
|
@@ -70,6 +70,17 @@ export type {
|
|
|
70
70
|
ContractsFreshnessBaseline,
|
|
71
71
|
CoverageBaseline,
|
|
72
72
|
MetaBaselines,
|
|
73
|
+
// SOC 2 Compliance types
|
|
74
|
+
Soc2ControlArea,
|
|
75
|
+
Soc2ControlStatus,
|
|
76
|
+
Soc2ControlResult,
|
|
77
|
+
Soc2ComplianceOutput,
|
|
78
|
+
// SOC 2 Guard Configuration types
|
|
79
|
+
SecretsDetectionConfig,
|
|
80
|
+
StructuredLoggingConfig,
|
|
81
|
+
AlertsConfigGuardConfig,
|
|
82
|
+
BackupConfigGuardConfig,
|
|
83
|
+
Soc2Baselines,
|
|
73
84
|
} from './guards/index.js'
|
|
74
85
|
|
|
75
86
|
export {
|
|
@@ -78,6 +89,11 @@ export {
|
|
|
78
89
|
DEFAULT_SKIP_DIRECTORIES,
|
|
79
90
|
DEFAULT_DOMAIN_SECTIONS,
|
|
80
91
|
DEFAULT_INFRA_SECTIONS,
|
|
92
|
+
// SOC 2 Compliance constants
|
|
93
|
+
SOC2_CONTROL_NAMES,
|
|
94
|
+
REQUIRED_SOC2_CONTROLS,
|
|
95
|
+
BLOCKING_SOC2_CONTROLS,
|
|
96
|
+
SOC2_SCHEMA_VERSION,
|
|
81
97
|
} from './guards/index.js'
|
|
82
98
|
|
|
83
99
|
// Compatibility manifest (CI guard vocabulary)
|
|
@@ -117,6 +133,25 @@ export type {
|
|
|
117
133
|
AssistantMessagePart,
|
|
118
134
|
PartBuilderState,
|
|
119
135
|
AddPartResult,
|
|
136
|
+
// Preview types (Phase 3)
|
|
137
|
+
PreviewArtifactKind,
|
|
138
|
+
PreviewArtifact,
|
|
139
|
+
PreviewData,
|
|
140
|
+
PreviewPart,
|
|
141
|
+
PreviewDataPart,
|
|
142
|
+
// Confirmation types (Phase 4)
|
|
143
|
+
ConfirmationChoice,
|
|
144
|
+
ConfirmationRisk,
|
|
145
|
+
ConfirmationData,
|
|
146
|
+
ConfirmationDataPart,
|
|
147
|
+
ConfirmationResponseData,
|
|
148
|
+
ConfirmationResponsePart,
|
|
149
|
+
ConfirmationResponseDataPart,
|
|
150
|
+
// Execution result types (Phase 5)
|
|
151
|
+
ExecutionArtifactStatus,
|
|
152
|
+
ExecutionResultData,
|
|
153
|
+
ExecutionResultPart,
|
|
154
|
+
ExecutionResultDataPart,
|
|
120
155
|
} from './message-parts/index.js'
|
|
121
156
|
|
|
122
157
|
export {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { WireSurfaceBuilder } from '../wire.js'
|
|
3
|
+
import type { ConfirmationData, ConfirmationResponseData } from '../confirmation.js'
|
|
4
|
+
|
|
5
|
+
describe('WireSurfaceBuilder.confirmation', () => {
|
|
6
|
+
it('creates a confirmation data part', () => {
|
|
7
|
+
const data: ConfirmationData = {
|
|
8
|
+
actionId: 'msg-123',
|
|
9
|
+
title: 'Send Slack message to #sales',
|
|
10
|
+
prompt: 'Confirm sending this message?',
|
|
11
|
+
risk: 'low',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const result = WireSurfaceBuilder.confirmation(data)
|
|
15
|
+
|
|
16
|
+
expect(result.type).toBe('data-confirmation')
|
|
17
|
+
expect(result.data).toEqual(data)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('preserves actionId exactly', () => {
|
|
21
|
+
const data: ConfirmationData = {
|
|
22
|
+
actionId: 'complex-action-id-12345',
|
|
23
|
+
title: 'Test',
|
|
24
|
+
prompt: 'Confirm?',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = WireSurfaceBuilder.confirmation(data)
|
|
28
|
+
|
|
29
|
+
expect(result.data.actionId).toBe('complex-action-id-12345')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('handles confirmation without optional risk level', () => {
|
|
33
|
+
const data: ConfirmationData = {
|
|
34
|
+
actionId: 'no-risk-specified',
|
|
35
|
+
title: 'Simple action',
|
|
36
|
+
prompt: 'Do you want to proceed?',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = WireSurfaceBuilder.confirmation(data)
|
|
40
|
+
|
|
41
|
+
expect(result.type).toBe('data-confirmation')
|
|
42
|
+
expect(result.data.risk).toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('preserves all risk levels', () => {
|
|
46
|
+
const risks: Array<'low' | 'medium' | 'high'> = ['low', 'medium', 'high']
|
|
47
|
+
|
|
48
|
+
for (const risk of risks) {
|
|
49
|
+
const data: ConfirmationData = {
|
|
50
|
+
actionId: `risk-${risk}`,
|
|
51
|
+
title: `${risk} risk action`,
|
|
52
|
+
prompt: 'Confirm?',
|
|
53
|
+
risk,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = WireSurfaceBuilder.confirmation(data)
|
|
57
|
+
|
|
58
|
+
expect(result.data.risk).toBe(risk)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('WireSurfaceBuilder.confirmationResponse', () => {
|
|
64
|
+
it('creates a confirmation response for confirm choice', () => {
|
|
65
|
+
const data: ConfirmationResponseData = {
|
|
66
|
+
actionId: 'msg-123',
|
|
67
|
+
choice: 'confirm',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = WireSurfaceBuilder.confirmationResponse(data)
|
|
71
|
+
|
|
72
|
+
expect(result.type).toBe('data-confirmation-response')
|
|
73
|
+
expect(result.data.actionId).toBe('msg-123')
|
|
74
|
+
expect(result.data.choice).toBe('confirm')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('creates a confirmation response for cancel choice', () => {
|
|
78
|
+
const data: ConfirmationResponseData = {
|
|
79
|
+
actionId: 'msg-123',
|
|
80
|
+
choice: 'cancel',
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = WireSurfaceBuilder.confirmationResponse(data)
|
|
84
|
+
|
|
85
|
+
expect(result.type).toBe('data-confirmation-response')
|
|
86
|
+
expect(result.data.choice).toBe('cancel')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('preserves actionId exactly in response', () => {
|
|
90
|
+
const data: ConfirmationResponseData = {
|
|
91
|
+
actionId: 'complex-action-id-12345',
|
|
92
|
+
choice: 'confirm',
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = WireSurfaceBuilder.confirmationResponse(data)
|
|
96
|
+
|
|
97
|
+
expect(result.data.actionId).toBe('complex-action-id-12345')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { WireSurfaceBuilder } from '../wire.js'
|
|
3
|
+
import type { PreviewData, PreviewArtifact } from '../preview.js'
|
|
4
|
+
|
|
5
|
+
describe('WireSurfaceBuilder.preview', () => {
|
|
6
|
+
it('creates a preview data part', () => {
|
|
7
|
+
const data: PreviewData = {
|
|
8
|
+
actionId: 'msg-123',
|
|
9
|
+
title: 'Send Slack message to #sales',
|
|
10
|
+
description: 'This will notify the sales team about the new pricing.',
|
|
11
|
+
artifacts: [
|
|
12
|
+
{
|
|
13
|
+
kind: 'message',
|
|
14
|
+
label: '#sales',
|
|
15
|
+
content: {
|
|
16
|
+
channel: 'C123456',
|
|
17
|
+
text: 'New pricing is live!',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = WireSurfaceBuilder.preview(data)
|
|
24
|
+
|
|
25
|
+
expect(result.type).toBe('data-preview')
|
|
26
|
+
expect(result.data).toEqual(data)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('preserves artifact structure exactly', () => {
|
|
30
|
+
const artifacts: PreviewArtifact[] = [
|
|
31
|
+
{
|
|
32
|
+
kind: 'api_call',
|
|
33
|
+
label: 'POST /api/notify',
|
|
34
|
+
content: { method: 'POST', body: { message: 'test' } },
|
|
35
|
+
metadata: { target: 'internal-api', impact: 'low' },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
kind: 'notification',
|
|
39
|
+
label: 'Email to team@example.com',
|
|
40
|
+
content: { to: 'team@example.com', subject: 'Update' },
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const data: PreviewData = {
|
|
45
|
+
actionId: 'multi-123',
|
|
46
|
+
title: 'Multi-step action',
|
|
47
|
+
artifacts,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = WireSurfaceBuilder.preview(data)
|
|
51
|
+
|
|
52
|
+
expect(result.data.artifacts).toEqual(artifacts)
|
|
53
|
+
expect(result.data.artifacts[0].metadata?.impact).toBe('low')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('handles empty artifacts array', () => {
|
|
57
|
+
const data: PreviewData = {
|
|
58
|
+
actionId: 'empty-preview',
|
|
59
|
+
title: 'No actions preview',
|
|
60
|
+
artifacts: [],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result = WireSurfaceBuilder.preview(data)
|
|
64
|
+
|
|
65
|
+
expect(result.type).toBe('data-preview')
|
|
66
|
+
expect(result.data.artifacts).toEqual([])
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('preserves all artifact kinds', () => {
|
|
70
|
+
const allKinds: PreviewArtifact[] = [
|
|
71
|
+
{ kind: 'message', label: 'Slack message', content: {} },
|
|
72
|
+
{ kind: 'api_call', label: 'API call', content: {} },
|
|
73
|
+
{ kind: 'diff', label: 'File change', content: {} },
|
|
74
|
+
{ kind: 'notification', label: 'Notification', content: {} },
|
|
75
|
+
{ kind: 'task', label: 'Task creation', content: {} },
|
|
76
|
+
{ kind: 'calendar', label: 'Calendar event', content: {} },
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
const data: PreviewData = {
|
|
80
|
+
actionId: 'all-kinds',
|
|
81
|
+
title: 'All artifact kinds',
|
|
82
|
+
artifacts: allKinds,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = WireSurfaceBuilder.preview(data)
|
|
86
|
+
|
|
87
|
+
expect(result.data.artifacts).toHaveLength(6)
|
|
88
|
+
expect(result.data.artifacts.map((a) => a.kind)).toEqual([
|
|
89
|
+
'message',
|
|
90
|
+
'api_call',
|
|
91
|
+
'diff',
|
|
92
|
+
'notification',
|
|
93
|
+
'task',
|
|
94
|
+
'calendar',
|
|
95
|
+
])
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirmation Surface Types
|
|
3
|
+
*
|
|
4
|
+
* Confirmation surfaces request explicit user decision on a proposed action.
|
|
5
|
+
* Only structured signals are accepted - no natural language inference.
|
|
6
|
+
*
|
|
7
|
+
* CONFIRMATION MODE INVARIANTS:
|
|
8
|
+
* - Confirmation is deterministic and system-owned
|
|
9
|
+
* - No tool calls are allowed in confirmation mode
|
|
10
|
+
* - Confirmation must reference a specific actionId
|
|
11
|
+
* - Only structured confirmation-response signals are accepted
|
|
12
|
+
* - Natural language is never used to infer confirmation ("yes", "go ahead" = invalid)
|
|
13
|
+
* - Execution is impossible in Phase 4 - only Phase 5 can execute
|
|
14
|
+
* - At most one action is confirmable at a time
|
|
15
|
+
* - Mismatched actionId fails closed and re-prompts
|
|
16
|
+
*
|
|
17
|
+
* @see DECISIONS.md for design rationale
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Choice options for confirmation.
|
|
22
|
+
*/
|
|
23
|
+
export type ConfirmationChoice = 'confirm' | 'cancel';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Risk level for confirmation prompts.
|
|
27
|
+
*/
|
|
28
|
+
export type ConfirmationRisk = 'low' | 'medium' | 'high';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Confirmation request data payload.
|
|
32
|
+
*/
|
|
33
|
+
export interface ConfirmationData {
|
|
34
|
+
/** Must match the active preview's actionId */
|
|
35
|
+
actionId: string;
|
|
36
|
+
/** Summary of the action being confirmed */
|
|
37
|
+
title: string;
|
|
38
|
+
/** The confirmation prompt, e.g. "Confirm sending this Slack message?" */
|
|
39
|
+
prompt: string;
|
|
40
|
+
/** Optional risk level for UI styling */
|
|
41
|
+
risk?: ConfirmationRisk;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Confirmation request message part (semantic type).
|
|
46
|
+
*/
|
|
47
|
+
export interface ConfirmationPart {
|
|
48
|
+
type: 'confirmation';
|
|
49
|
+
data: ConfirmationData;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Confirmation request data part (wire format).
|
|
54
|
+
*/
|
|
55
|
+
export interface ConfirmationDataPart {
|
|
56
|
+
type: 'data-confirmation';
|
|
57
|
+
data: ConfirmationData;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// === Confirmation Response Types (New in Phase 4) ===
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Confirmation response data payload.
|
|
64
|
+
* Sent by the user (via UI button or structured message) to confirm/cancel.
|
|
65
|
+
*/
|
|
66
|
+
export interface ConfirmationResponseData {
|
|
67
|
+
/** Must match the active confirmation's actionId */
|
|
68
|
+
actionId: string;
|
|
69
|
+
/** The user's decision */
|
|
70
|
+
choice: ConfirmationChoice;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Confirmation response message part (semantic type).
|
|
75
|
+
*/
|
|
76
|
+
export interface ConfirmationResponsePart {
|
|
77
|
+
type: 'confirmation-response';
|
|
78
|
+
data: ConfirmationResponseData;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Confirmation response data part (wire format).
|
|
83
|
+
*/
|
|
84
|
+
export interface ConfirmationResponseDataPart {
|
|
85
|
+
type: 'data-confirmation-response';
|
|
86
|
+
data: ConfirmationResponseData;
|
|
87
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Result Surface Types
|
|
3
|
+
*
|
|
4
|
+
* Surfaces for displaying execution results to the user.
|
|
5
|
+
*
|
|
6
|
+
* EXECUTION MODE INVARIANTS:
|
|
7
|
+
* - Execution occurs only after confirmation with matching actionId
|
|
8
|
+
* - Each actionId executes at most once (idempotency)
|
|
9
|
+
* - All executions produce audit records (success or failure)
|
|
10
|
+
* - Execution is synchronous and deterministic
|
|
11
|
+
* - Tools are explicitly allowlisted per artifact kind
|
|
12
|
+
* - No retries, no background work, no rollback
|
|
13
|
+
* - Failures stop execution immediately
|
|
14
|
+
*
|
|
15
|
+
* @see DECISIONS.md for design rationale
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { PreviewArtifactKind } from './preview.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Status of a single artifact's execution.
|
|
22
|
+
*/
|
|
23
|
+
export interface ExecutionArtifactStatus {
|
|
24
|
+
kind: PreviewArtifactKind;
|
|
25
|
+
label: string;
|
|
26
|
+
status: 'success' | 'failed' | 'skipped';
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Execution result data payload.
|
|
32
|
+
*/
|
|
33
|
+
export interface ExecutionResultData {
|
|
34
|
+
actionId: string;
|
|
35
|
+
status: 'success' | 'failed';
|
|
36
|
+
artifacts: ExecutionArtifactStatus[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execution result message part (semantic type).
|
|
41
|
+
*/
|
|
42
|
+
export interface ExecutionResultPart {
|
|
43
|
+
type: 'execution-result';
|
|
44
|
+
data: ExecutionResultData;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Execution result data part (wire format).
|
|
49
|
+
*/
|
|
50
|
+
export interface ExecutionResultDataPart {
|
|
51
|
+
type: 'data-execution-result';
|
|
52
|
+
data: ExecutionResultData;
|
|
53
|
+
}
|
|
@@ -14,11 +14,39 @@ export type {
|
|
|
14
14
|
ChartPart,
|
|
15
15
|
ChartDataPoint,
|
|
16
16
|
TablePart,
|
|
17
|
-
ConfirmationPart,
|
|
18
17
|
SurfacePart,
|
|
19
18
|
AssistantMessagePart,
|
|
20
19
|
} from './types.js'
|
|
21
20
|
|
|
21
|
+
// Preview types
|
|
22
|
+
export type {
|
|
23
|
+
PreviewArtifactKind,
|
|
24
|
+
PreviewArtifact,
|
|
25
|
+
PreviewData,
|
|
26
|
+
PreviewPart,
|
|
27
|
+
PreviewDataPart,
|
|
28
|
+
} from './preview.js'
|
|
29
|
+
|
|
30
|
+
// Confirmation types
|
|
31
|
+
export type {
|
|
32
|
+
ConfirmationChoice,
|
|
33
|
+
ConfirmationRisk,
|
|
34
|
+
ConfirmationData,
|
|
35
|
+
ConfirmationPart,
|
|
36
|
+
ConfirmationDataPart,
|
|
37
|
+
ConfirmationResponseData,
|
|
38
|
+
ConfirmationResponsePart,
|
|
39
|
+
ConfirmationResponseDataPart,
|
|
40
|
+
} from './confirmation.js'
|
|
41
|
+
|
|
42
|
+
// Execution result types
|
|
43
|
+
export type {
|
|
44
|
+
ExecutionArtifactStatus,
|
|
45
|
+
ExecutionResultData,
|
|
46
|
+
ExecutionResultPart,
|
|
47
|
+
ExecutionResultDataPart,
|
|
48
|
+
} from './execution.js'
|
|
49
|
+
|
|
22
50
|
// Type guards
|
|
23
51
|
export { isTextPart, isSurfacePart } from './types.js'
|
|
24
52
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Surface Types
|
|
3
|
+
*
|
|
4
|
+
* Preview surfaces represent exactly what would happen if an action were executed.
|
|
5
|
+
* They are read-only by contract and contain no side effects.
|
|
6
|
+
*
|
|
7
|
+
* PREVIEW MODE INVARIANTS:
|
|
8
|
+
* - Preview surfaces are read-only
|
|
9
|
+
* - Preview mode allows zero side effects
|
|
10
|
+
* - Preview artifacts must be exactly executable later
|
|
11
|
+
* - Execution is impossible without a later mode transition
|
|
12
|
+
* - LLMs never execute actions in preview mode
|
|
13
|
+
* - Preview mode is entered when system emits data-preview surface (not user intent)
|
|
14
|
+
* - User messages during preview do not affect mode; only assistant output controls persistence
|
|
15
|
+
* - At most one preview proposal is active at a time (multiple previews are sequential, not concurrent)
|
|
16
|
+
*
|
|
17
|
+
* @see DECISIONS.md for design rationale
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Preview artifact kinds.
|
|
22
|
+
* Each kind represents a different type of action that could be executed.
|
|
23
|
+
*/
|
|
24
|
+
export type PreviewArtifactKind =
|
|
25
|
+
| 'message' // Slack/chat message to send
|
|
26
|
+
| 'api_call' // External API call
|
|
27
|
+
| 'diff' // File or document change
|
|
28
|
+
| 'notification' // Notification to user/team
|
|
29
|
+
| 'task' // Task creation
|
|
30
|
+
| 'calendar' // Calendar event
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A single artifact within a preview.
|
|
34
|
+
* Represents one atomic action that would be taken.
|
|
35
|
+
*/
|
|
36
|
+
export interface PreviewArtifact {
|
|
37
|
+
/** The kind of action this artifact represents */
|
|
38
|
+
kind: PreviewArtifactKind
|
|
39
|
+
/** Human-readable label for this artifact */
|
|
40
|
+
label: string
|
|
41
|
+
/** The exact payload that would be executed - must be JSON-safe */
|
|
42
|
+
content: unknown
|
|
43
|
+
/** Optional metadata for display purposes */
|
|
44
|
+
metadata?: {
|
|
45
|
+
/** Target system (e.g., "slack", "google-calendar") */
|
|
46
|
+
target?: string
|
|
47
|
+
/** Estimated impact level */
|
|
48
|
+
impact?: 'low' | 'medium' | 'high'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Preview surface data payload.
|
|
54
|
+
*/
|
|
55
|
+
export interface PreviewData {
|
|
56
|
+
/** Stable identifier for this preview - used for confirmation tracking */
|
|
57
|
+
actionId: string
|
|
58
|
+
/** Human-readable summary of what will happen */
|
|
59
|
+
title: string
|
|
60
|
+
/** Optional detailed explanation */
|
|
61
|
+
description?: string
|
|
62
|
+
/** The artifacts that would be created/sent/modified */
|
|
63
|
+
artifacts: PreviewArtifact[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Preview message part (semantic type).
|
|
68
|
+
*/
|
|
69
|
+
export interface PreviewPart {
|
|
70
|
+
type: 'preview'
|
|
71
|
+
data: PreviewData
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Preview data part (wire format).
|
|
76
|
+
* Uses AI SDK's data-{name} convention.
|
|
77
|
+
*/
|
|
78
|
+
export interface PreviewDataPart {
|
|
79
|
+
type: 'data-preview'
|
|
80
|
+
data: PreviewData
|
|
81
|
+
}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { ToolListMessagePart } from '../mcp/index.js'
|
|
17
|
+
import type { PreviewPart } from './preview.js'
|
|
18
|
+
import type { ConfirmationPart, ConfirmationResponsePart } from './confirmation.js'
|
|
17
19
|
|
|
18
20
|
// =============================================================================
|
|
19
21
|
// Narrative Parts (Streamable)
|
|
@@ -102,20 +104,6 @@ export interface TablePart {
|
|
|
102
104
|
rows: string[][]
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
/**
|
|
106
|
-
* Confirmation request surface part.
|
|
107
|
-
* Requests user confirmation before proceeding.
|
|
108
|
-
*/
|
|
109
|
-
export interface ConfirmationPart {
|
|
110
|
-
type: 'confirmation'
|
|
111
|
-
/** What is being confirmed */
|
|
112
|
-
action: string
|
|
113
|
-
/** Optional details */
|
|
114
|
-
details?: string
|
|
115
|
-
/** Confirmation ID for response tracking */
|
|
116
|
-
confirmationId: string
|
|
117
|
-
}
|
|
118
|
-
|
|
119
107
|
// =============================================================================
|
|
120
108
|
// Part Unions
|
|
121
109
|
// =============================================================================
|
|
@@ -123,6 +111,9 @@ export interface ConfirmationPart {
|
|
|
123
111
|
/**
|
|
124
112
|
* Surface parts are rendered atomically (never streamed).
|
|
125
113
|
* Extensible: add new surface types to this union.
|
|
114
|
+
*
|
|
115
|
+
* Note: ConfirmationResponsePart is user-originated but included
|
|
116
|
+
* here for uniform parsing. Do not render as UI surface.
|
|
126
117
|
*/
|
|
127
118
|
export type SurfacePart =
|
|
128
119
|
| ToolListPart
|
|
@@ -130,6 +121,8 @@ export type SurfacePart =
|
|
|
130
121
|
| ChartPart
|
|
131
122
|
| TablePart
|
|
132
123
|
| ConfirmationPart
|
|
124
|
+
| ConfirmationResponsePart
|
|
125
|
+
| PreviewPart
|
|
133
126
|
|
|
134
127
|
/**
|
|
135
128
|
* All assistant message part types.
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { MCPToolDescriptor, ToolListDataPart } from '../mcp/index.js'
|
|
14
|
+
import type { PreviewDataPart, PreviewData } from './preview.js'
|
|
15
|
+
import type {
|
|
16
|
+
ConfirmationData,
|
|
17
|
+
ConfirmationDataPart,
|
|
18
|
+
ConfirmationResponseData,
|
|
19
|
+
ConfirmationResponseDataPart,
|
|
20
|
+
} from './confirmation.js'
|
|
21
|
+
import type { ExecutionResultData, ExecutionResultDataPart } from './execution.js'
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
24
|
* Factory for creating wire-format surface parts.
|
|
@@ -37,4 +45,78 @@ export const WireSurfaceBuilder = {
|
|
|
37
45
|
data: { tools },
|
|
38
46
|
}
|
|
39
47
|
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build a preview data part for streaming.
|
|
51
|
+
* Previews show exactly what would happen if an action were executed.
|
|
52
|
+
*
|
|
53
|
+
* INVARIANTS:
|
|
54
|
+
* - Preview data must be exactly executable later
|
|
55
|
+
* - No placeholders or approximations
|
|
56
|
+
* - actionId must be stable for confirmation tracking
|
|
57
|
+
*
|
|
58
|
+
* @param data - Preview data containing actionId, title, and artifacts
|
|
59
|
+
* @returns Wire-format preview part ready for stream
|
|
60
|
+
*/
|
|
61
|
+
preview(data: PreviewData): PreviewDataPart {
|
|
62
|
+
return {
|
|
63
|
+
type: 'data-preview',
|
|
64
|
+
data,
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build a confirmation request data part for streaming.
|
|
70
|
+
* Requests explicit user decision on a proposed action.
|
|
71
|
+
*
|
|
72
|
+
* INVARIANTS:
|
|
73
|
+
* - actionId must match the active preview
|
|
74
|
+
* - Only structured responses are accepted
|
|
75
|
+
*
|
|
76
|
+
* @param data - Confirmation data containing actionId, title, and prompt
|
|
77
|
+
* @returns Wire-format confirmation part ready for stream
|
|
78
|
+
*/
|
|
79
|
+
confirmation(data: ConfirmationData): ConfirmationDataPart {
|
|
80
|
+
return {
|
|
81
|
+
type: 'data-confirmation',
|
|
82
|
+
data,
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a confirmation response data part.
|
|
88
|
+
* Used when processing user's confirm/cancel decision.
|
|
89
|
+
*
|
|
90
|
+
* INVARIANTS:
|
|
91
|
+
* - actionId must match the active confirmation
|
|
92
|
+
* - choice must be 'confirm' or 'cancel'
|
|
93
|
+
*
|
|
94
|
+
* @param data - Response data containing actionId and choice
|
|
95
|
+
* @returns Wire-format confirmation response part
|
|
96
|
+
*/
|
|
97
|
+
confirmationResponse(data: ConfirmationResponseData): ConfirmationResponseDataPart {
|
|
98
|
+
return {
|
|
99
|
+
type: 'data-confirmation-response',
|
|
100
|
+
data,
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build an execution result data part for streaming.
|
|
106
|
+
* Reports the outcome of an executed action.
|
|
107
|
+
*
|
|
108
|
+
* INVARIANTS:
|
|
109
|
+
* - actionId must match the executed action
|
|
110
|
+
* - All artifacts have a status (success, failed, or skipped)
|
|
111
|
+
* - Execution is complete and audited before this is sent
|
|
112
|
+
*
|
|
113
|
+
* @param data - Execution result data
|
|
114
|
+
* @returns Wire-format execution result part ready for stream
|
|
115
|
+
*/
|
|
116
|
+
executionResult(data: ExecutionResultData): ExecutionResultDataPart {
|
|
117
|
+
return {
|
|
118
|
+
type: 'data-execution-result',
|
|
119
|
+
data,
|
|
120
|
+
}
|
|
121
|
+
},
|
|
40
122
|
} as const
|