@company-semantics/contracts 9.1.0 → 9.2.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 +4 -1
- package/src/__tests__/resource-keys.test.ts +30 -23
- package/src/admin/authz-simulate.ts +4 -4
- package/src/admin/direct-grants.ts +2 -2
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +97 -0
- package/src/api/http/routes/ai-chat.ts +3 -3
- package/src/api/http/utils/resource-response.ts +5 -2
- package/src/api/index.ts +4 -4
- package/src/api/primitives.ts +6 -2
- package/src/auth/README.md +1 -0
- package/src/auth/index.ts +12 -5
- package/src/autotune.ts +5 -1
- package/src/billing/index.ts +1 -1
- package/src/billing/types.ts +1 -1
- package/src/chat/README.md +3 -0
- package/src/chat/__tests__/runtime-profile.test.ts +68 -48
- package/src/chat/index.ts +10 -4
- package/src/chat/runtime-profile.ts +25 -10
- package/src/chat/schemas.ts +49 -41
- package/src/chat/types.ts +48 -42
- package/src/ci-envelope/README.md +2 -0
- package/src/ci-envelope/__tests__/transitions.test.ts +56 -56
- package/src/ci-envelope/index.ts +2 -2
- package/src/ci-envelope/types.ts +20 -20
- package/src/ci-results/index.ts +2 -2
- package/src/ci-results/repo-ci-result.ts +15 -12
- package/src/compatibility.ts +6 -6
- package/src/content/index.ts +10 -4
- package/src/content/schemas.ts +42 -24
- package/src/dispatch/index.ts +18 -15
- package/src/email/__tests__/registry.test.ts +81 -77
- package/src/email/index.ts +3 -3
- package/src/email/registry.ts +25 -25
- package/src/email/types.ts +43 -43
- package/src/errors/index.ts +8 -8
- package/src/execution/__tests__/events.test.ts +42 -42
- package/src/execution/__tests__/lifecycle.test.ts +192 -190
- package/src/execution/__tests__/registry.test.ts +114 -114
- package/src/execution/audit-export.ts +4 -4
- package/src/execution/errors.ts +7 -7
- package/src/execution/event-metadata.ts +4 -4
- package/src/execution/events.ts +23 -21
- package/src/execution/expiry.ts +5 -5
- package/src/execution/hash-chain.ts +2 -2
- package/src/execution/index.ts +19 -28
- package/src/execution/kinds.ts +7 -7
- package/src/execution/lifecycle.ts +33 -33
- package/src/execution/registry.ts +63 -63
- package/src/execution/schemas.ts +31 -23
- package/src/execution/status.ts +45 -26
- package/src/execution/summary.ts +16 -17
- package/src/execution/timeline-ui.ts +9 -9
- package/src/execution/types.ts +31 -25
- package/src/generated/openapi-routes.ts +1 -0
- package/src/guards/config.ts +22 -18
- package/src/guards/index.ts +4 -4
- package/src/guards/types.ts +32 -24
- package/src/identity/__tests__/avatar.test.ts +68 -59
- package/src/identity/avatar.ts +8 -8
- package/src/identity/display-name.ts +3 -3
- package/src/identity/index.ts +8 -8
- package/src/identity/people-org-chart.ts +8 -4
- package/src/identity/schemas.ts +28 -18
- package/src/identity/types.ts +5 -5
- package/src/impersonation/index.ts +5 -5
- package/src/impersonation/schemas.ts +15 -9
- package/src/impersonation-events.ts +21 -21
- package/src/impersonation.ts +25 -24
- package/src/index.ts +118 -90
- package/src/interfaces/mcp/tools/help.ts +19 -19
- package/src/internal-admin.ts +6 -6
- package/src/mcp/README.md +2 -0
- package/src/mcp/__tests__/capability-graph.test.ts +290 -290
- package/src/mcp/capability-graph.ts +42 -40
- package/src/mcp/failure-context.ts +1 -3
- package/src/mcp/index.ts +57 -57
- package/src/mcp/resources.ts +9 -9
- package/src/meetings/index.ts +2 -2
- package/src/meetings/schemas.ts +51 -34
- package/src/message-parts/README.md +2 -0
- package/src/message-parts/__tests__/builder.test.ts +142 -142
- package/src/message-parts/__tests__/confirmation.test.ts +100 -86
- package/src/message-parts/__tests__/preview.test.ts +63 -63
- package/src/message-parts/__tests__/wire.test.ts +130 -124
- package/src/message-parts/builder.ts +23 -23
- package/src/message-parts/confirmation.ts +17 -14
- package/src/message-parts/execution.ts +7 -7
- package/src/message-parts/index.ts +10 -10
- package/src/message-parts/lifecycle.ts +25 -25
- package/src/message-parts/preview.ts +30 -30
- package/src/message-parts/types.ts +27 -27
- package/src/message-parts/wire.ts +24 -24
- package/src/mutations.ts +2 -2
- package/src/observability.ts +23 -11
- package/src/org/__tests__/org-units.test.ts +131 -96
- package/src/org/__tests__/tree-ordering.test.ts +57 -37
- package/src/org/__tests__/view-scopes.test.ts +40 -40
- package/src/org/domain.ts +9 -9
- package/src/org/index.ts +24 -21
- package/src/org/org-units.ts +34 -20
- package/src/org/schemas.ts +201 -127
- package/src/org/sharing.ts +17 -13
- package/src/org/tree-ordering.ts +3 -1
- package/src/org/types.ts +54 -47
- package/src/org/view-scopes.ts +9 -9
- package/src/permissions/access-levels.ts +7 -2
- package/src/permissions/access-source.ts +6 -6
- package/src/permissions/index.ts +5 -5
- package/src/permissions/orgchart-roles.ts +7 -7
- package/src/permissions/permission-introspection.ts +7 -5
- package/src/permissions/share-api.ts +19 -9
- package/src/pressure.ts +4 -4
- package/src/queryIntent.ts +21 -21
- package/src/ralph/__tests__/prd-groups.test.ts +159 -159
- package/src/ralph/__tests__/prd.test.ts +30 -30
- package/src/ralph/index.ts +3 -8
- package/src/ralph/prd.ts +33 -33
- package/src/ralph/progress.ts +1 -1
- package/src/rate-limit/README.md +4 -4
- package/src/rate-limit/index.ts +3 -3
- package/src/requests.ts +36 -8
- package/src/resource-keys.ts +207 -124
- package/src/resource-registry.ts +5 -5
- package/src/route-builder.ts +3 -3
- package/src/safe-mode.ts +2 -2
- package/src/security/index.ts +4 -4
- package/src/security/org-secrets.ts +13 -9
- package/src/security/secret.ts +3 -3
- package/src/sse.ts +3 -1
- package/src/system/README.md +3 -0
- package/src/system/capabilities.ts +22 -23
- package/src/system/diagram.ts +45 -45
- package/src/system/index.ts +14 -14
- package/src/tiers.ts +1 -1
- package/src/timeouts.ts +1 -1
- package/src/tracing.ts +30 -30
- package/src/types/analytics.ts +2 -2
- package/src/usage/README.md +3 -0
- package/src/usage/execution-types.ts +69 -69
- package/src/usage/types.ts +7 -3
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @see ADR-CONT-029 for design rationale
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { ISO8601Timestamp } from
|
|
16
|
+
import type { ISO8601Timestamp } from "./types";
|
|
17
17
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// Timeline Status
|
|
@@ -23,7 +23,7 @@ import type { ISO8601Timestamp } from './types'
|
|
|
23
23
|
* Status for timeline display.
|
|
24
24
|
* Maps from ExecutionStatus for UI-specific presentation.
|
|
25
25
|
*/
|
|
26
|
-
export type TimelineStatus =
|
|
26
|
+
export type TimelineStatus = "success" | "failed" | "pending";
|
|
27
27
|
|
|
28
28
|
// =============================================================================
|
|
29
29
|
// Timeline Icon
|
|
@@ -33,7 +33,7 @@ export type TimelineStatus = 'success' | 'failed' | 'pending'
|
|
|
33
33
|
* Icon for timeline items.
|
|
34
34
|
* Must match IconName but constrained to timeline-relevant icons.
|
|
35
35
|
*/
|
|
36
|
-
export type TimelineIcon =
|
|
36
|
+
export type TimelineIcon = "plug" | "unlink";
|
|
37
37
|
|
|
38
38
|
// =============================================================================
|
|
39
39
|
// Timeline UI Event
|
|
@@ -52,26 +52,26 @@ export type TimelineIcon = 'plug' | 'unlink'
|
|
|
52
52
|
*/
|
|
53
53
|
export interface TimelineUIEvent {
|
|
54
54
|
/** Execution ID for correlation and detail fetching */
|
|
55
|
-
executionId: string
|
|
55
|
+
executionId: string;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Display label (past tense).
|
|
59
59
|
* Derived from EXECUTION_KINDS[kind].display.pastTenseLabel
|
|
60
60
|
*/
|
|
61
|
-
label: string
|
|
61
|
+
label: string;
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Icon identifier.
|
|
65
65
|
* Derived from EXECUTION_KINDS[kind].display.icon
|
|
66
66
|
*/
|
|
67
|
-
icon: TimelineIcon
|
|
67
|
+
icon: TimelineIcon;
|
|
68
68
|
|
|
69
69
|
/** Visual status indicator */
|
|
70
|
-
status: TimelineStatus
|
|
70
|
+
status: TimelineStatus;
|
|
71
71
|
|
|
72
72
|
/** When this event occurred */
|
|
73
|
-
timestamp: ISO8601Timestamp
|
|
73
|
+
timestamp: ISO8601Timestamp;
|
|
74
74
|
|
|
75
75
|
/** Whether this event has expandable details */
|
|
76
|
-
expandable: boolean
|
|
76
|
+
expandable: boolean;
|
|
77
77
|
}
|
package/src/execution/types.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* @see ADR-CONT-029 for design rationale
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ExecutionKind } from
|
|
10
|
-
import type { ConfirmationRiskLevel } from
|
|
9
|
+
import type { ExecutionKind } from "./kinds";
|
|
10
|
+
import type { ConfirmationRiskLevel } from "../message-parts/confirmation";
|
|
11
11
|
|
|
12
12
|
// =============================================================================
|
|
13
13
|
// Utility Types
|
|
@@ -17,13 +17,13 @@ import type { ConfirmationRiskLevel } from '../message-parts/confirmation'
|
|
|
17
17
|
* ISO 8601 timestamp string.
|
|
18
18
|
* Example: "2026-01-08T14:30:00.000Z"
|
|
19
19
|
*/
|
|
20
|
-
export type ISO8601Timestamp = string
|
|
20
|
+
export type ISO8601Timestamp = string;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Icon names for execution kinds.
|
|
24
24
|
* Must be supported by the frontend icon system.
|
|
25
25
|
*/
|
|
26
|
-
export type IconName =
|
|
26
|
+
export type IconName = "plug" | "unlink" | "pencil" | "send";
|
|
27
27
|
|
|
28
28
|
// =============================================================================
|
|
29
29
|
// Execution Domain
|
|
@@ -32,7 +32,13 @@ export type IconName = 'plug' | 'unlink' | 'pencil' | 'send'
|
|
|
32
32
|
/**
|
|
33
33
|
* Domain categories for execution kinds.
|
|
34
34
|
*/
|
|
35
|
-
export type ExecutionDomain =
|
|
35
|
+
export type ExecutionDomain =
|
|
36
|
+
| "integration"
|
|
37
|
+
| "policy"
|
|
38
|
+
| "data"
|
|
39
|
+
| "system"
|
|
40
|
+
| "profile"
|
|
41
|
+
| "communication";
|
|
36
42
|
|
|
37
43
|
// =============================================================================
|
|
38
44
|
// Execution Kind Definition
|
|
@@ -50,46 +56,46 @@ export type ExecutionDomain = 'integration' | 'policy' | 'data' | 'system' | 'pr
|
|
|
50
56
|
*/
|
|
51
57
|
export interface ExecutionKindDefinition {
|
|
52
58
|
/** The execution kind this definition describes */
|
|
53
|
-
kind: ExecutionKind
|
|
59
|
+
kind: ExecutionKind;
|
|
54
60
|
|
|
55
61
|
/** Domain category for grouping and filtering */
|
|
56
|
-
domain: ExecutionDomain
|
|
62
|
+
domain: ExecutionDomain;
|
|
57
63
|
|
|
58
64
|
/** Display metadata for UI rendering */
|
|
59
65
|
display: {
|
|
60
66
|
/** Label shown on buttons/actions (imperative: "Connect Slack") */
|
|
61
|
-
label: string
|
|
67
|
+
label: string;
|
|
62
68
|
/** Label for completed actions (past tense: "Slack connected") */
|
|
63
|
-
pastTenseLabel: string
|
|
69
|
+
pastTenseLabel: string;
|
|
64
70
|
/** Icon identifier for UI */
|
|
65
|
-
icon: IconName
|
|
66
|
-
}
|
|
71
|
+
icon: IconName;
|
|
72
|
+
};
|
|
67
73
|
|
|
68
74
|
/** Governance constraints */
|
|
69
75
|
governance: {
|
|
70
76
|
/** Who can see this execution in lists/timelines */
|
|
71
|
-
visibility:
|
|
77
|
+
visibility: "admin" | "user";
|
|
72
78
|
/** Whether admin role is required to initiate */
|
|
73
|
-
requiresAdmin: boolean
|
|
79
|
+
requiresAdmin: boolean;
|
|
74
80
|
/** If reversible, which kind reverses this action */
|
|
75
|
-
reversibleBy?: ExecutionKind
|
|
76
|
-
}
|
|
81
|
+
reversibleBy?: ExecutionKind;
|
|
82
|
+
};
|
|
77
83
|
|
|
78
84
|
/** UI behavior hints */
|
|
79
85
|
ui: {
|
|
80
86
|
/** Show in admin settings panel */
|
|
81
|
-
showInAdmin: boolean
|
|
87
|
+
showInAdmin: boolean;
|
|
82
88
|
/** Show in user-facing timeline */
|
|
83
|
-
showInTimeline: boolean
|
|
89
|
+
showInTimeline: boolean;
|
|
84
90
|
/** Require confirmation dialog before execution */
|
|
85
|
-
confirmBeforeRun?: boolean
|
|
86
|
-
}
|
|
91
|
+
confirmBeforeRun?: boolean;
|
|
92
|
+
};
|
|
87
93
|
|
|
88
94
|
/** Explanation generation metadata */
|
|
89
95
|
explanation: {
|
|
90
96
|
/** Template ID for explanation builder */
|
|
91
|
-
templateId: string
|
|
92
|
-
}
|
|
97
|
+
templateId: string;
|
|
98
|
+
};
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
// =============================================================================
|
|
@@ -116,8 +122,8 @@ export interface ExecutionKindDefinition {
|
|
|
116
122
|
* from the execution domain barrel only, NOT from root index.ts.
|
|
117
123
|
*/
|
|
118
124
|
export interface ExecutionIntent {
|
|
119
|
-
executionKind: ExecutionKind
|
|
120
|
-
requiresConfirmation: boolean
|
|
121
|
-
requiresApproval: boolean
|
|
122
|
-
risk: ConfirmationRiskLevel
|
|
125
|
+
executionKind: ExecutionKind;
|
|
126
|
+
requiresConfirmation: boolean;
|
|
127
|
+
requiresApproval: boolean;
|
|
128
|
+
risk: ConfirmationRiskLevel;
|
|
123
129
|
}
|
|
@@ -48,6 +48,7 @@ export const openApiRoutes = {
|
|
|
48
48
|
'/api/executions/{executionId}/timeline': ['GET'],
|
|
49
49
|
'/api/executions/{executionId}/undo': ['POST'],
|
|
50
50
|
'/api/factory/floor': ['GET'],
|
|
51
|
+
'/api/factory/snapshot': ['GET'],
|
|
51
52
|
'/api/internal-admin/impersonate/end': ['POST'],
|
|
52
53
|
'/api/internal-admin/impersonate/session': ['GET'],
|
|
53
54
|
'/api/internal-admin/impersonate/start': ['POST'],
|
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, Soc2ControlArea } from
|
|
11
|
+
import type { CheckResult, Soc2ControlArea } from "./types";
|
|
12
12
|
|
|
13
13
|
// =============================================================================
|
|
14
14
|
// Size Limits
|
|
@@ -101,7 +101,7 @@ export interface GuardConfig {
|
|
|
101
101
|
*/
|
|
102
102
|
export type GuardCheck = (
|
|
103
103
|
repoRoot: string,
|
|
104
|
-
config: GuardConfig
|
|
104
|
+
config: GuardConfig,
|
|
105
105
|
) => Promise<CheckResult>;
|
|
106
106
|
|
|
107
107
|
/**
|
|
@@ -109,7 +109,7 @@ export type GuardCheck = (
|
|
|
109
109
|
* Factories create check functions with optional overrides.
|
|
110
110
|
*/
|
|
111
111
|
export type GuardCheckFactory = (
|
|
112
|
-
overrides?: Partial<GuardConfig
|
|
112
|
+
overrides?: Partial<GuardConfig>,
|
|
113
113
|
) => GuardCheck;
|
|
114
114
|
|
|
115
115
|
// =============================================================================
|
|
@@ -138,7 +138,11 @@ export interface ContractsFreshnessBaseline {
|
|
|
138
138
|
*/
|
|
139
139
|
compatibility: {
|
|
140
140
|
minSupported: string;
|
|
141
|
-
deprecations: readonly {
|
|
141
|
+
deprecations: readonly {
|
|
142
|
+
range: string;
|
|
143
|
+
reason: string;
|
|
144
|
+
removeIn: string;
|
|
145
|
+
}[];
|
|
142
146
|
};
|
|
143
147
|
|
|
144
148
|
/**
|
|
@@ -563,30 +567,30 @@ export interface GuardCheckRegistry {
|
|
|
563
567
|
* Default directories to skip when walking file trees.
|
|
564
568
|
*/
|
|
565
569
|
export const DEFAULT_SKIP_DIRECTORIES = [
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
570
|
+
"node_modules",
|
|
571
|
+
"dist",
|
|
572
|
+
".git",
|
|
573
|
+
".next",
|
|
574
|
+
"coverage",
|
|
575
|
+
".tsbuild",
|
|
572
576
|
];
|
|
573
577
|
|
|
574
578
|
/**
|
|
575
579
|
* Default README sections for domain directories.
|
|
576
580
|
*/
|
|
577
581
|
export const DEFAULT_DOMAIN_SECTIONS = [
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
+
"Purpose",
|
|
583
|
+
"Invariants",
|
|
584
|
+
"Public API",
|
|
585
|
+
"Dependencies",
|
|
582
586
|
];
|
|
583
587
|
|
|
584
588
|
/**
|
|
585
589
|
* Default README sections for infrastructure directories.
|
|
586
590
|
*/
|
|
587
591
|
export const DEFAULT_INFRA_SECTIONS = [
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
+
"Purpose",
|
|
593
|
+
"How it works",
|
|
594
|
+
"Integration",
|
|
595
|
+
"Maintenance",
|
|
592
596
|
];
|
package/src/guards/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type {
|
|
|
23
23
|
Soc2ControlStatus,
|
|
24
24
|
Soc2ControlResult,
|
|
25
25
|
Soc2ComplianceOutput,
|
|
26
|
-
} from
|
|
26
|
+
} from "./types";
|
|
27
27
|
|
|
28
28
|
// Constants (these are type-level, not runtime)
|
|
29
29
|
export {
|
|
@@ -34,7 +34,7 @@ export {
|
|
|
34
34
|
REQUIRED_SOC2_CONTROLS,
|
|
35
35
|
BLOCKING_SOC2_CONTROLS,
|
|
36
36
|
SOC2_SCHEMA_VERSION,
|
|
37
|
-
} from
|
|
37
|
+
} from "./types";
|
|
38
38
|
|
|
39
39
|
// Config types
|
|
40
40
|
export type {
|
|
@@ -63,11 +63,11 @@ export type {
|
|
|
63
63
|
AlertsConfigGuardConfig,
|
|
64
64
|
BackupConfigGuardConfig,
|
|
65
65
|
Soc2Baselines,
|
|
66
|
-
} from
|
|
66
|
+
} from "./config";
|
|
67
67
|
|
|
68
68
|
// Config constants (type-level defaults)
|
|
69
69
|
export {
|
|
70
70
|
DEFAULT_SKIP_DIRECTORIES,
|
|
71
71
|
DEFAULT_DOMAIN_SECTIONS,
|
|
72
72
|
DEFAULT_INFRA_SECTIONS,
|
|
73
|
-
} from
|
|
73
|
+
} from "./config";
|
package/src/guards/types.ts
CHANGED
|
@@ -58,7 +58,12 @@ export interface GuardTierResult {
|
|
|
58
58
|
* - evolution: uncovered patterns, growth tracking (observational)
|
|
59
59
|
* - meta: guards that protect the guard system itself (coverage correctness)
|
|
60
60
|
*/
|
|
61
|
-
export type GuardTier =
|
|
61
|
+
export type GuardTier =
|
|
62
|
+
| "structural"
|
|
63
|
+
| "behavioral"
|
|
64
|
+
| "invariants"
|
|
65
|
+
| "evolution"
|
|
66
|
+
| "meta";
|
|
62
67
|
|
|
63
68
|
/**
|
|
64
69
|
* Full guard results, organized by tier.
|
|
@@ -114,18 +119,18 @@ export interface GuardOutput {
|
|
|
114
119
|
* Current schema version.
|
|
115
120
|
* v2.0.0: Tiered results (breaking change from v1.x flattened structure)
|
|
116
121
|
*/
|
|
117
|
-
export const GUARD_SCHEMA_VERSION =
|
|
122
|
+
export const GUARD_SCHEMA_VERSION = "2.0.0";
|
|
118
123
|
|
|
119
124
|
/**
|
|
120
125
|
* All guard tiers in order.
|
|
121
126
|
* Useful for iteration when aggregating results.
|
|
122
127
|
*/
|
|
123
128
|
export const GUARD_TIERS: readonly GuardTier[] = [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
"structural",
|
|
130
|
+
"behavioral",
|
|
131
|
+
"invariants",
|
|
132
|
+
"evolution",
|
|
133
|
+
"meta",
|
|
129
134
|
] as const;
|
|
130
135
|
|
|
131
136
|
// =============================================================================
|
|
@@ -172,7 +177,7 @@ export interface VulnerabilityConfig {
|
|
|
172
177
|
* - BR: Backup & Recovery
|
|
173
178
|
* - AI: Audit Integrity
|
|
174
179
|
*/
|
|
175
|
-
export type Soc2ControlArea =
|
|
180
|
+
export type Soc2ControlArea = "CM" | "AC" | "LM" | "SD" | "BR" | "AI";
|
|
176
181
|
|
|
177
182
|
/**
|
|
178
183
|
* Control status semantics:
|
|
@@ -186,18 +191,18 @@ export type Soc2ControlArea = 'CM' | 'AC' | 'LM' | 'SD' | 'BR' | 'AI';
|
|
|
186
191
|
* - Is never blocking
|
|
187
192
|
* - Must be explicit (configured in soc2Baselines), not implicit
|
|
188
193
|
*/
|
|
189
|
-
export type Soc2ControlStatus =
|
|
194
|
+
export type Soc2ControlStatus = "PASS" | "WARN" | "FAIL" | "SKIP";
|
|
190
195
|
|
|
191
196
|
/**
|
|
192
197
|
* Human-readable names for SOC 2 control areas.
|
|
193
198
|
*/
|
|
194
199
|
export const SOC2_CONTROL_NAMES: Record<Soc2ControlArea, string> = {
|
|
195
|
-
CM:
|
|
196
|
-
AC:
|
|
197
|
-
LM:
|
|
198
|
-
SD:
|
|
199
|
-
BR:
|
|
200
|
-
AI:
|
|
200
|
+
CM: "Change Management",
|
|
201
|
+
AC: "Access Control",
|
|
202
|
+
LM: "Logging & Monitoring",
|
|
203
|
+
SD: "Secure SDLC",
|
|
204
|
+
BR: "Backup & Recovery",
|
|
205
|
+
AI: "Audit Integrity",
|
|
201
206
|
} as const;
|
|
202
207
|
|
|
203
208
|
/**
|
|
@@ -205,19 +210,22 @@ export const SOC2_CONTROL_NAMES: Record<Soc2ControlArea, string> = {
|
|
|
205
210
|
* Aggregator fails if any control is missing from the output.
|
|
206
211
|
*/
|
|
207
212
|
export const REQUIRED_SOC2_CONTROLS: readonly Soc2ControlArea[] = [
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
"CM",
|
|
214
|
+
"AC",
|
|
215
|
+
"LM",
|
|
216
|
+
"SD",
|
|
217
|
+
"BR",
|
|
218
|
+
"AI",
|
|
214
219
|
] as const;
|
|
215
220
|
|
|
216
221
|
/**
|
|
217
222
|
* Controls that block CI when status is FAIL.
|
|
218
223
|
* Non-blocking controls report FAIL but don't set exit code to 1.
|
|
219
224
|
*/
|
|
220
|
-
export const BLOCKING_SOC2_CONTROLS: readonly Soc2ControlArea[] = [
|
|
225
|
+
export const BLOCKING_SOC2_CONTROLS: readonly Soc2ControlArea[] = [
|
|
226
|
+
"CM",
|
|
227
|
+
"AC",
|
|
228
|
+
] as const;
|
|
221
229
|
|
|
222
230
|
/**
|
|
223
231
|
* A single SOC 2 control assertion result.
|
|
@@ -264,11 +272,11 @@ export interface Soc2ComplianceOutput {
|
|
|
264
272
|
/** All control results (must include all REQUIRED_SOC2_CONTROLS) */
|
|
265
273
|
controls: Soc2ControlResult[];
|
|
266
274
|
/** Overall pass/fail based on blocking controls only */
|
|
267
|
-
overallStatus:
|
|
275
|
+
overallStatus: "PASS" | "FAIL";
|
|
268
276
|
}
|
|
269
277
|
|
|
270
278
|
/**
|
|
271
279
|
* Current SOC 2 compliance schema version.
|
|
272
280
|
* Bump on breaking schema changes.
|
|
273
281
|
*/
|
|
274
|
-
export const SOC2_SCHEMA_VERSION =
|
|
282
|
+
export const SOC2_SCHEMA_VERSION = "1.0.0";
|
|
@@ -1,75 +1,84 @@
|
|
|
1
|
-
import { describe, it, expect } from
|
|
2
|
-
import { generateInitials, resolveAvatar } from
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { generateInitials, resolveAvatar } from "../avatar.js";
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
6
|
-
expect(generateInitials(
|
|
7
|
-
})
|
|
4
|
+
describe("generateInitials", () => {
|
|
5
|
+
it("returns single uppercase initial for single-word name", () => {
|
|
6
|
+
expect(generateInitials("Madonna")).toBe("M");
|
|
7
|
+
});
|
|
8
8
|
|
|
9
|
-
it(
|
|
10
|
-
expect(generateInitials(
|
|
11
|
-
})
|
|
9
|
+
it("returns first+last uppercase initials for two-word name", () => {
|
|
10
|
+
expect(generateInitials("Ian Heidt")).toBe("IH");
|
|
11
|
+
});
|
|
12
12
|
|
|
13
|
-
it(
|
|
14
|
-
expect(generateInitials(
|
|
15
|
-
})
|
|
13
|
+
it("returns first+last initials, skipping middle for multi-word name", () => {
|
|
14
|
+
expect(generateInitials("Mary Jane Doe")).toBe("MD");
|
|
15
|
+
});
|
|
16
16
|
|
|
17
|
-
it(
|
|
18
|
-
expect(generateInitials(
|
|
19
|
-
})
|
|
17
|
+
it("returns empty string for empty input", () => {
|
|
18
|
+
expect(generateInitials("")).toBe("");
|
|
19
|
+
});
|
|
20
20
|
|
|
21
|
-
it(
|
|
22
|
-
expect(generateInitials(
|
|
23
|
-
})
|
|
21
|
+
it("returns empty string for whitespace-only input", () => {
|
|
22
|
+
expect(generateInitials(" ")).toBe("");
|
|
23
|
+
});
|
|
24
24
|
|
|
25
|
-
it(
|
|
26
|
-
expect(generateInitials(
|
|
27
|
-
})
|
|
25
|
+
it("handles name with extra whitespace", () => {
|
|
26
|
+
expect(generateInitials(" Ian Heidt ")).toBe("IH");
|
|
27
|
+
});
|
|
28
28
|
|
|
29
|
-
it(
|
|
30
|
-
expect(generateInitials(
|
|
31
|
-
})
|
|
29
|
+
it("returns single character for single character name", () => {
|
|
30
|
+
expect(generateInitials("X")).toBe("X");
|
|
31
|
+
});
|
|
32
32
|
|
|
33
|
-
it(
|
|
34
|
-
expect(generateInitials(
|
|
35
|
-
})
|
|
36
|
-
})
|
|
33
|
+
it("returns uppercase for lowercase input", () => {
|
|
34
|
+
expect(generateInitials("ian heidt")).toBe("IH");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
37
|
|
|
38
|
-
describe(
|
|
39
|
-
it(
|
|
40
|
-
const result = resolveAvatar({
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
describe("resolveAvatar", () => {
|
|
39
|
+
it("initials is always populated when source is photo (critical invariant)", () => {
|
|
40
|
+
const result = resolveAvatar({
|
|
41
|
+
avatarUrl: "https://example.com/img.jpg",
|
|
42
|
+
fullName: "Ian Heidt",
|
|
43
|
+
});
|
|
44
|
+
expect(result.source).toBe("photo");
|
|
45
|
+
expect(result.url).toBe("https://example.com/img.jpg");
|
|
43
46
|
// INVARIANT: initials is ALWAYS populated, regardless of source.
|
|
44
47
|
// This prevents UI regressions when the photo fails to load.
|
|
45
|
-
expect(typeof result.initials).toBe(
|
|
46
|
-
expect(result.initials).toBe(
|
|
47
|
-
})
|
|
48
|
+
expect(typeof result.initials).toBe("string");
|
|
49
|
+
expect(result.initials).toBe("IH");
|
|
50
|
+
});
|
|
48
51
|
|
|
49
|
-
it(
|
|
50
|
-
const result = resolveAvatar({ fullName:
|
|
51
|
-
expect(result.source).toBe(
|
|
52
|
-
expect(result.initials).toBe(
|
|
53
|
-
expect(result.url).toBeUndefined()
|
|
54
|
-
})
|
|
52
|
+
it("returns initials source when no avatarUrl", () => {
|
|
53
|
+
const result = resolveAvatar({ fullName: "Ian Heidt" } as any);
|
|
54
|
+
expect(result.source).toBe("initials");
|
|
55
|
+
expect(result.initials).toBe("IH");
|
|
56
|
+
expect(result.url).toBeUndefined();
|
|
57
|
+
});
|
|
55
58
|
|
|
56
|
-
it(
|
|
57
|
-
const result = resolveAvatar({
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
it("returns photo source with empty initials when fullName is empty", () => {
|
|
60
|
+
const result = resolveAvatar({
|
|
61
|
+
avatarUrl: "https://example.com/img.jpg",
|
|
62
|
+
fullName: "",
|
|
63
|
+
});
|
|
64
|
+
expect(result.source).toBe("photo");
|
|
65
|
+
expect(result.url).toBe("https://example.com/img.jpg");
|
|
60
66
|
// Empty is valid — just not undefined
|
|
61
|
-
expect(result.initials).toBe(
|
|
62
|
-
})
|
|
67
|
+
expect(result.initials).toBe("");
|
|
68
|
+
});
|
|
63
69
|
|
|
64
|
-
it(
|
|
65
|
-
const result = resolveAvatar({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
it("returns initials source when avatarUrl is explicitly undefined", () => {
|
|
71
|
+
const result = resolveAvatar({
|
|
72
|
+
avatarUrl: undefined,
|
|
73
|
+
fullName: "Ian Heidt",
|
|
74
|
+
});
|
|
75
|
+
expect(result.source).toBe("initials");
|
|
76
|
+
expect(result.initials).toBe("IH");
|
|
77
|
+
});
|
|
69
78
|
|
|
70
|
-
it(
|
|
71
|
-
const result = resolveAvatar({ avatarUrl:
|
|
72
|
-
expect(result.source).toBe(
|
|
73
|
-
expect(result.initials).toBe(
|
|
74
|
-
})
|
|
75
|
-
})
|
|
79
|
+
it("returns initials source when avatarUrl is empty string (falsy)", () => {
|
|
80
|
+
const result = resolveAvatar({ avatarUrl: "", fullName: "Ian Heidt" });
|
|
81
|
+
expect(result.source).toBe("initials");
|
|
82
|
+
expect(result.initials).toBe("IH");
|
|
83
|
+
});
|
|
84
|
+
});
|
package/src/identity/avatar.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @see ADR-BE-063 for design rationale
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { UserIdentity } from
|
|
10
|
+
import type { UserIdentity } from "./types";
|
|
11
11
|
|
|
12
12
|
// =============================================================================
|
|
13
13
|
// Types
|
|
@@ -18,7 +18,7 @@ import type { UserIdentity } from './types';
|
|
|
18
18
|
* - 'photo': Using a resolved profile photo
|
|
19
19
|
* - 'initials': Using generated initials (fallback)
|
|
20
20
|
*/
|
|
21
|
-
export type AvatarSource =
|
|
21
|
+
export type AvatarSource = "photo" | "initials";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Resolved avatar for UI rendering.
|
|
@@ -61,16 +61,16 @@ export interface ResolvedAvatar {
|
|
|
61
61
|
*/
|
|
62
62
|
export function generateInitials(fullName: string): string {
|
|
63
63
|
const trimmed = fullName.trim();
|
|
64
|
-
if (!trimmed) return
|
|
64
|
+
if (!trimmed) return "";
|
|
65
65
|
|
|
66
66
|
const words = trimmed.split(/\s+/);
|
|
67
|
-
const firstInitial = words[0]?.[0]?.toUpperCase() ??
|
|
67
|
+
const firstInitial = words[0]?.[0]?.toUpperCase() ?? "";
|
|
68
68
|
|
|
69
69
|
if (words.length === 1) {
|
|
70
70
|
return firstInitial;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const lastInitial = words[words.length - 1]?.[0]?.toUpperCase() ??
|
|
73
|
+
const lastInitial = words[words.length - 1]?.[0]?.toUpperCase() ?? "";
|
|
74
74
|
return `${firstInitial}${lastInitial}`;
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -100,20 +100,20 @@ export function generateInitials(fullName: string): string {
|
|
|
100
100
|
* // { source: 'initials', initials: 'IH' }
|
|
101
101
|
*/
|
|
102
102
|
export function resolveAvatar(
|
|
103
|
-
identity: Pick<UserIdentity,
|
|
103
|
+
identity: Pick<UserIdentity, "avatarUrl" | "fullName">,
|
|
104
104
|
): ResolvedAvatar {
|
|
105
105
|
const initials = generateInitials(identity.fullName);
|
|
106
106
|
|
|
107
107
|
if (identity.avatarUrl) {
|
|
108
108
|
return {
|
|
109
|
-
source:
|
|
109
|
+
source: "photo",
|
|
110
110
|
url: identity.avatarUrl,
|
|
111
111
|
initials,
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
return {
|
|
116
|
-
source:
|
|
116
|
+
source: "initials",
|
|
117
117
|
initials,
|
|
118
118
|
};
|
|
119
119
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @see ADR-CONT-032 for design rationale
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { UserIdentity } from
|
|
10
|
+
import type { UserIdentity } from "./types";
|
|
11
11
|
|
|
12
12
|
// =============================================================================
|
|
13
13
|
// Helper Functions
|
|
@@ -28,7 +28,7 @@ import type { UserIdentity } from './types';
|
|
|
28
28
|
*/
|
|
29
29
|
export function extractFirstWord(fullName: string): string {
|
|
30
30
|
const trimmed = fullName.trim();
|
|
31
|
-
const spaceIndex = trimmed.indexOf(
|
|
31
|
+
const spaceIndex = trimmed.indexOf(" ");
|
|
32
32
|
return spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -57,7 +57,7 @@ export function extractFirstWord(fullName: string): string {
|
|
|
57
57
|
* resolveDisplayName({ fullName: "Madonna" }) // "Madonna"
|
|
58
58
|
*/
|
|
59
59
|
export function resolveDisplayName(
|
|
60
|
-
identity: Pick<UserIdentity,
|
|
60
|
+
identity: Pick<UserIdentity, "preferredName" | "fullName">,
|
|
61
61
|
): string {
|
|
62
62
|
return identity.preferredName?.trim() || extractFirstWord(identity.fullName);
|
|
63
63
|
}
|