@company-semantics/contracts 9.1.0 → 10.0.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 +117 -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 +191 -136
- 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
package/src/ralph/progress.ts
CHANGED
package/src/rate-limit/README.md
CHANGED
|
@@ -18,8 +18,8 @@ Configuration for rate limit windows:
|
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
20
|
interface RateLimitConfig {
|
|
21
|
-
maxRequests: number;
|
|
22
|
-
windowMs: number;
|
|
21
|
+
maxRequests: number; // Maximum requests allowed in window
|
|
22
|
+
windowMs: number; // Window duration in milliseconds
|
|
23
23
|
}
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -29,9 +29,9 @@ Result of a rate limit check:
|
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
31
|
interface RateLimitResult {
|
|
32
|
-
allowed: boolean;
|
|
32
|
+
allowed: boolean; // true = permitted, false = rate limited
|
|
33
33
|
remaining: number; // Requests remaining in current window
|
|
34
|
-
resetAt: number;
|
|
34
|
+
resetAt: number; // Unix timestamp when window resets
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
package/src/rate-limit/index.ts
CHANGED
package/src/requests.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export type Tier =
|
|
1
|
+
export type Tier = "P0" | "P1" | "P2" | "P3";
|
|
2
2
|
|
|
3
|
-
export type MutationBehavior =
|
|
3
|
+
export type MutationBehavior = "collapse" | "queue" | "parallel";
|
|
4
4
|
|
|
5
5
|
export type SchedulerResourceKey = {
|
|
6
6
|
type: string;
|
|
@@ -9,7 +9,7 @@ export type SchedulerResourceKey = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export type RequestDescriptor = {
|
|
12
|
-
method:
|
|
12
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
13
13
|
path: string;
|
|
14
14
|
params?: Record<string, unknown>;
|
|
15
15
|
tier: Tier;
|
|
@@ -21,8 +21,36 @@ export type RequestDescriptor = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export type SchedulerEvent =
|
|
24
|
-
| {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
| {
|
|
25
|
+
type: "enqueued";
|
|
26
|
+
id: string;
|
|
27
|
+
descriptor: RequestDescriptor;
|
|
28
|
+
queuedAt: number;
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: "started";
|
|
32
|
+
id: string;
|
|
33
|
+
descriptor: RequestDescriptor;
|
|
34
|
+
startedAt: number;
|
|
35
|
+
waitedMs: number;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
type: "settled";
|
|
39
|
+
id: string;
|
|
40
|
+
descriptor: RequestDescriptor;
|
|
41
|
+
durationMs: number;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
status?: number;
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
type: "aborted";
|
|
47
|
+
id: string;
|
|
48
|
+
descriptor: RequestDescriptor;
|
|
49
|
+
reason: "superseded" | "invalidated" | "unmounted" | "external";
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
type: "deduped";
|
|
53
|
+
id: string;
|
|
54
|
+
descriptor: RequestDescriptor;
|
|
55
|
+
sharedWith: string;
|
|
56
|
+
};
|
package/src/resource-keys.ts
CHANGED
|
@@ -5,52 +5,56 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type ResourceKey =
|
|
7
7
|
// Collections (plural) — lists of entities
|
|
8
|
-
| { type:
|
|
9
|
-
| { type:
|
|
10
|
-
| { type:
|
|
11
|
-
| { type:
|
|
12
|
-
| { type:
|
|
13
|
-
| { type:
|
|
14
|
-
| { type:
|
|
15
|
-
| { type:
|
|
8
|
+
| { type: "members"; orgId: string }
|
|
9
|
+
| { type: "departments"; orgId: string }
|
|
10
|
+
| { type: "chats"; orgId: string }
|
|
11
|
+
| { type: "teams"; orgId: string }
|
|
12
|
+
| { type: "integrations"; orgId: string }
|
|
13
|
+
| { type: "invites"; orgId: string }
|
|
14
|
+
| { type: "auditEvents"; orgId: string }
|
|
15
|
+
| { type: "timeline"; orgId: string }
|
|
16
16
|
// Identities (singular) — single entities
|
|
17
|
-
| { type:
|
|
18
|
-
| { type:
|
|
19
|
-
| { type:
|
|
20
|
-
| { type:
|
|
21
|
-
| { type:
|
|
22
|
-
| { type:
|
|
23
|
-
| { type:
|
|
24
|
-
| { type:
|
|
25
|
-
| { type:
|
|
26
|
-
| { type:
|
|
27
|
-
| { type:
|
|
28
|
-
| { type:
|
|
29
|
-
| { type:
|
|
30
|
-
| { type:
|
|
17
|
+
| { type: "member"; orgId: string; memberId: string }
|
|
18
|
+
| { type: "team"; orgId: string; teamId: string }
|
|
19
|
+
| { type: "department"; orgId: string; departmentId: string }
|
|
20
|
+
| { type: "chat"; orgId: string; chatId: string }
|
|
21
|
+
| { type: "companyMdDoc"; orgId: string; slug: string }
|
|
22
|
+
| { type: "companyMdContextBank"; orgId: string; slug: string }
|
|
23
|
+
| { type: "workspace"; orgId: string }
|
|
24
|
+
| { type: "workspaceDomains"; orgId: string }
|
|
25
|
+
| { type: "authSettings"; orgId: string }
|
|
26
|
+
| { type: "billing"; orgId: string }
|
|
27
|
+
| { type: "aiUsage"; orgId: string }
|
|
28
|
+
| { type: "deletionEligibility"; orgId: string }
|
|
29
|
+
| { type: "transferOwnership"; orgId: string }
|
|
30
|
+
| { type: "companyMdDocs"; orgId: string }
|
|
31
31
|
// OrgUnit canonical model (ADR-BE-120) — Phase 2 Wave 4
|
|
32
|
-
| { type:
|
|
33
|
-
| { type:
|
|
34
|
-
| { type:
|
|
35
|
-
| { type:
|
|
36
|
-
| { type:
|
|
37
|
-
| { type:
|
|
38
|
-
| { type:
|
|
32
|
+
| { type: "orgTree"; orgId: string }
|
|
33
|
+
| { type: "orgLevelConfig"; orgId: string }
|
|
34
|
+
| { type: "orgUnit"; orgId: string; unitId: string }
|
|
35
|
+
| { type: "orgUnitChildren"; orgId: string; unitId: string }
|
|
36
|
+
| { type: "orgUnitAncestors"; orgId: string; unitId: string }
|
|
37
|
+
| { type: "orgUnitMemberships"; orgId: string; unitId: string }
|
|
38
|
+
| { type: "orgUnitPermissions"; orgId: string; unitId: string }
|
|
39
39
|
// Org-unit owners list (ADR-CONTRACTS-052) — owners are an org-wide
|
|
40
40
|
// projection, not a per-unit collection, so unitId is intentionally excluded.
|
|
41
|
-
| { type:
|
|
41
|
+
| { type: "orgUnitOwners"; orgId: string }
|
|
42
42
|
// People reporting (ADR-BE-166) — drives the settings Org chart drill-down
|
|
43
|
-
| { type:
|
|
43
|
+
| { type: "peopleOrgChart"; orgId: string }
|
|
44
44
|
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources.
|
|
45
45
|
// No orgId/userId: these live above any single org. scope is the literal 'system'.
|
|
46
|
-
| { type:
|
|
47
|
-
| { type:
|
|
48
|
-
| { type:
|
|
46
|
+
| { type: "internalAdminAiProviders"; scope: "system" }
|
|
47
|
+
| { type: "internalAdminPrompts"; scope: "system" }
|
|
48
|
+
| { type: "internalAdminAiRuntimeDefaults"; scope: "system" }
|
|
49
|
+
// Software-factory surfaces (ADR-BE-239 / ADR-BE-243) — internal-admin
|
|
50
|
+
// dashboard reads over Global-infra factory tables; tenant-less, scope 'system'.
|
|
51
|
+
| { type: "factoryFloor"; scope: "system" }
|
|
52
|
+
| { type: "factorySnapshot"; scope: "system" }
|
|
49
53
|
// User-scoped
|
|
50
|
-
| { type:
|
|
51
|
-
| { type:
|
|
52
|
-
| { type:
|
|
53
|
-
| { type:
|
|
54
|
+
| { type: "dismissedBanners"; userId: string }
|
|
55
|
+
| { type: "userOrgs"; userId: string }
|
|
56
|
+
| { type: "sessions"; userId: string }
|
|
57
|
+
| { type: "viewer"; userId: string };
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
* Action — structured mutation key used across execution system, audit, permissions.
|
|
@@ -69,13 +73,34 @@ export function resolveScope(context: {
|
|
|
69
73
|
|
|
70
74
|
/** All ResourceKey type literals for exhaustive checking. */
|
|
71
75
|
const ORG_SCOPED_TYPES = [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
"members",
|
|
77
|
+
"departments",
|
|
78
|
+
"chats",
|
|
79
|
+
"teams",
|
|
80
|
+
"integrations",
|
|
81
|
+
"invites",
|
|
82
|
+
"auditEvents",
|
|
83
|
+
"timeline",
|
|
84
|
+
"workspace",
|
|
85
|
+
"workspaceDomains",
|
|
86
|
+
"authSettings",
|
|
87
|
+
"billing",
|
|
88
|
+
"aiUsage",
|
|
89
|
+
"deletionEligibility",
|
|
90
|
+
"transferOwnership",
|
|
91
|
+
"companyMdDocs",
|
|
92
|
+
"orgTree",
|
|
93
|
+
"orgLevelConfig",
|
|
94
|
+
"peopleOrgChart",
|
|
95
|
+
"orgUnitOwners",
|
|
76
96
|
] as const;
|
|
77
97
|
|
|
78
|
-
const USER_SCOPED_TYPES = [
|
|
98
|
+
const USER_SCOPED_TYPES = [
|
|
99
|
+
"dismissedBanners",
|
|
100
|
+
"userOrgs",
|
|
101
|
+
"sessions",
|
|
102
|
+
"viewer",
|
|
103
|
+
] as const;
|
|
79
104
|
|
|
80
105
|
/**
|
|
81
106
|
* System-scoped types (ADR-CONTRACTS-052) — tenant-less super-admin resources.
|
|
@@ -83,7 +108,11 @@ const USER_SCOPED_TYPES = ['dismissedBanners', 'userOrgs', 'sessions', 'viewer']
|
|
|
83
108
|
* Intentionally NOT exported — internal to the parser like the other scope arrays.
|
|
84
109
|
*/
|
|
85
110
|
const SYSTEM_SCOPED_TYPES = [
|
|
86
|
-
|
|
111
|
+
"internalAdminAiProviders",
|
|
112
|
+
"internalAdminPrompts",
|
|
113
|
+
"internalAdminAiRuntimeDefaults",
|
|
114
|
+
"factoryFloor",
|
|
115
|
+
"factorySnapshot",
|
|
87
116
|
] as const;
|
|
88
117
|
|
|
89
118
|
/**
|
|
@@ -98,60 +127,62 @@ const SYSTEM_SCOPED_TYPES = [
|
|
|
98
127
|
export function toQueryKey(key: ResourceKey): readonly string[] {
|
|
99
128
|
switch (key.type) {
|
|
100
129
|
// Identities with extra field
|
|
101
|
-
case
|
|
130
|
+
case "member":
|
|
102
131
|
return [key.type, key.orgId, key.memberId] as const;
|
|
103
|
-
case
|
|
132
|
+
case "team":
|
|
104
133
|
return [key.type, key.orgId, key.teamId] as const;
|
|
105
|
-
case
|
|
134
|
+
case "department":
|
|
106
135
|
return [key.type, key.orgId, key.departmentId] as const;
|
|
107
|
-
case
|
|
136
|
+
case "chat":
|
|
108
137
|
return [key.type, key.orgId, key.chatId] as const;
|
|
109
|
-
case
|
|
110
|
-
case
|
|
138
|
+
case "companyMdDoc":
|
|
139
|
+
case "companyMdContextBank":
|
|
111
140
|
return [key.type, key.orgId, key.slug] as const;
|
|
112
141
|
|
|
113
142
|
// OrgUnit identity keys (ADR-BE-120)
|
|
114
|
-
case
|
|
115
|
-
case
|
|
116
|
-
case
|
|
117
|
-
case
|
|
118
|
-
case
|
|
143
|
+
case "orgUnit":
|
|
144
|
+
case "orgUnitChildren":
|
|
145
|
+
case "orgUnitAncestors":
|
|
146
|
+
case "orgUnitMemberships":
|
|
147
|
+
case "orgUnitPermissions":
|
|
119
148
|
return [key.type, key.orgId, key.unitId] as const;
|
|
120
149
|
|
|
121
150
|
// User-scoped
|
|
122
|
-
case
|
|
123
|
-
case
|
|
124
|
-
case
|
|
125
|
-
case
|
|
151
|
+
case "dismissedBanners":
|
|
152
|
+
case "userOrgs":
|
|
153
|
+
case "sessions":
|
|
154
|
+
case "viewer":
|
|
126
155
|
return [key.type, key.userId] as const;
|
|
127
156
|
|
|
128
157
|
// Org-scoped collections and identities
|
|
129
|
-
case
|
|
130
|
-
case
|
|
131
|
-
case
|
|
132
|
-
case
|
|
133
|
-
case
|
|
134
|
-
case
|
|
135
|
-
case
|
|
136
|
-
case
|
|
137
|
-
case
|
|
138
|
-
case
|
|
139
|
-
case
|
|
140
|
-
case
|
|
141
|
-
case
|
|
142
|
-
case
|
|
143
|
-
case
|
|
144
|
-
case
|
|
145
|
-
case
|
|
146
|
-
case
|
|
147
|
-
case
|
|
148
|
-
case
|
|
158
|
+
case "members":
|
|
159
|
+
case "departments":
|
|
160
|
+
case "chats":
|
|
161
|
+
case "teams":
|
|
162
|
+
case "integrations":
|
|
163
|
+
case "invites":
|
|
164
|
+
case "auditEvents":
|
|
165
|
+
case "timeline":
|
|
166
|
+
case "workspace":
|
|
167
|
+
case "workspaceDomains":
|
|
168
|
+
case "authSettings":
|
|
169
|
+
case "billing":
|
|
170
|
+
case "aiUsage":
|
|
171
|
+
case "deletionEligibility":
|
|
172
|
+
case "transferOwnership":
|
|
173
|
+
case "companyMdDocs":
|
|
174
|
+
case "orgTree":
|
|
175
|
+
case "orgLevelConfig":
|
|
176
|
+
case "peopleOrgChart":
|
|
177
|
+
case "orgUnitOwners":
|
|
149
178
|
return [key.type, key.orgId] as const;
|
|
150
179
|
|
|
151
180
|
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources
|
|
152
|
-
case
|
|
153
|
-
case
|
|
154
|
-
case
|
|
181
|
+
case "internalAdminAiProviders":
|
|
182
|
+
case "internalAdminPrompts":
|
|
183
|
+
case "internalAdminAiRuntimeDefaults":
|
|
184
|
+
case "factoryFloor":
|
|
185
|
+
case "factorySnapshot":
|
|
155
186
|
return [key.type, key.scope] as const;
|
|
156
187
|
|
|
157
188
|
default: {
|
|
@@ -169,34 +200,42 @@ export function fromQueryKey(queryKey: readonly string[]): ResourceKey {
|
|
|
169
200
|
const [type, ...rest] = queryKey;
|
|
170
201
|
|
|
171
202
|
if (!type || rest.length === 0) {
|
|
172
|
-
throw new Error(
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Invalid query key: expected at least [type, scope], got ${JSON.stringify(queryKey)}`,
|
|
205
|
+
);
|
|
173
206
|
}
|
|
174
207
|
|
|
175
208
|
// Identities with extra field
|
|
176
209
|
const identityFields: Record<string, string> = {
|
|
177
|
-
member:
|
|
178
|
-
team:
|
|
179
|
-
department:
|
|
180
|
-
chat:
|
|
181
|
-
companyMdDoc:
|
|
182
|
-
companyMdContextBank:
|
|
183
|
-
orgUnit:
|
|
184
|
-
orgUnitChildren:
|
|
185
|
-
orgUnitAncestors:
|
|
186
|
-
orgUnitMemberships:
|
|
187
|
-
orgUnitPermissions:
|
|
210
|
+
member: "memberId",
|
|
211
|
+
team: "teamId",
|
|
212
|
+
department: "departmentId",
|
|
213
|
+
chat: "chatId",
|
|
214
|
+
companyMdDoc: "slug",
|
|
215
|
+
companyMdContextBank: "slug",
|
|
216
|
+
orgUnit: "unitId",
|
|
217
|
+
orgUnitChildren: "unitId",
|
|
218
|
+
orgUnitAncestors: "unitId",
|
|
219
|
+
orgUnitMemberships: "unitId",
|
|
220
|
+
orgUnitPermissions: "unitId",
|
|
188
221
|
};
|
|
189
222
|
|
|
190
223
|
if (type in identityFields) {
|
|
191
224
|
if (rest.length !== 2) {
|
|
192
|
-
throw new Error(
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Invalid query key for '${type}': expected [type, orgId, ${identityFields[type]}], got ${JSON.stringify(queryKey)}`,
|
|
227
|
+
);
|
|
193
228
|
}
|
|
194
|
-
return {
|
|
229
|
+
return {
|
|
230
|
+
type,
|
|
231
|
+
orgId: rest[0],
|
|
232
|
+
[identityFields[type]]: rest[1],
|
|
233
|
+
} as ResourceKey;
|
|
195
234
|
}
|
|
196
235
|
|
|
197
236
|
// System-scoped types (ADR-CONTRACTS-052) — query key is [type, 'system']
|
|
198
237
|
if ((SYSTEM_SCOPED_TYPES as readonly string[]).includes(type)) {
|
|
199
|
-
return { type, scope:
|
|
238
|
+
return { type, scope: "system" } as ResourceKey;
|
|
200
239
|
}
|
|
201
240
|
|
|
202
241
|
// User-scoped types
|
|
@@ -212,42 +251,17 @@ export function fromQueryKey(queryKey: readonly string[]): ResourceKey {
|
|
|
212
251
|
throw new Error(`Unknown resource type in query key: '${type}'`);
|
|
213
252
|
}
|
|
214
253
|
|
|
215
|
-
/**
|
|
216
|
-
* Bidirectional resource relationships.
|
|
217
|
-
* TanStack does NOT support prefix matching across different key shapes.
|
|
218
|
-
* When invalidating a collection, we must also invalidate related identity entries (and vice versa).
|
|
219
|
-
*/
|
|
220
|
-
export const resourceRelationships: Record<string, string[]> = {
|
|
221
|
-
members: ['member'],
|
|
222
|
-
member: ['members'],
|
|
223
|
-
departments: ['department'],
|
|
224
|
-
department: ['departments'],
|
|
225
|
-
chats: ['chat'],
|
|
226
|
-
chat: ['chats'],
|
|
227
|
-
teams: ['team'],
|
|
228
|
-
team: ['teams'],
|
|
229
|
-
companyMdDocs: ['companyMdDoc'],
|
|
230
|
-
companyMdDoc: ['companyMdDocs'],
|
|
231
|
-
// OrgUnit: reparent/create/archive invalidates the tree view of the whole org;
|
|
232
|
-
// membership mutations invalidate the unit + its memberships list.
|
|
233
|
-
orgTree: ['orgUnit', 'orgUnitChildren', 'orgUnitAncestors'],
|
|
234
|
-
orgUnit: ['orgTree'],
|
|
235
|
-
orgUnitChildren: ['orgTree'],
|
|
236
|
-
orgUnitAncestors: ['orgTree'],
|
|
237
|
-
// Subtree manage is driven by memberships (ADR-BE-151); mutating memberships
|
|
238
|
-
// invalidates both the direct roster and the effective-managers read.
|
|
239
|
-
orgUnitMemberships: ['orgUnit', 'orgUnitPermissions'],
|
|
240
|
-
orgUnitPermissions: ['orgUnitMemberships'],
|
|
241
|
-
};
|
|
242
|
-
|
|
243
254
|
/**
|
|
244
255
|
* Strict predicate for matching resource keys.
|
|
245
256
|
* Compares type + scope fields exactly. No partial matching. No loose comparisons.
|
|
246
257
|
* Used by invalidateResource with TanStack's predicate-based invalidation.
|
|
247
258
|
*/
|
|
248
|
-
export function matchesResourceKey(
|
|
259
|
+
export function matchesResourceKey(
|
|
260
|
+
queryKey: readonly unknown[],
|
|
261
|
+
targetKey: ResourceKey,
|
|
262
|
+
): boolean {
|
|
249
263
|
if (!Array.isArray(queryKey) && !isReadonlyArray(queryKey)) return false;
|
|
250
|
-
const stringKey = queryKey.filter((v): v is string => typeof v ===
|
|
264
|
+
const stringKey = queryKey.filter((v): v is string => typeof v === "string");
|
|
251
265
|
if (stringKey.length !== queryKey.length) return false;
|
|
252
266
|
|
|
253
267
|
let parsed: ResourceKey;
|
|
@@ -259,15 +273,56 @@ export function matchesResourceKey(queryKey: readonly unknown[], targetKey: Reso
|
|
|
259
273
|
|
|
260
274
|
if (parsed.type !== targetKey.type) return false;
|
|
261
275
|
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
if (
|
|
277
|
+
"orgId" in parsed &&
|
|
278
|
+
"orgId" in targetKey &&
|
|
279
|
+
parsed.orgId !== targetKey.orgId
|
|
280
|
+
)
|
|
281
|
+
return false;
|
|
282
|
+
if (
|
|
283
|
+
"userId" in parsed &&
|
|
284
|
+
"userId" in targetKey &&
|
|
285
|
+
parsed.userId !== targetKey.userId
|
|
286
|
+
)
|
|
287
|
+
return false;
|
|
288
|
+
if (
|
|
289
|
+
"memberId" in parsed &&
|
|
290
|
+
"memberId" in targetKey &&
|
|
291
|
+
parsed.memberId !== targetKey.memberId
|
|
292
|
+
)
|
|
293
|
+
return false;
|
|
294
|
+
if (
|
|
295
|
+
"teamId" in parsed &&
|
|
296
|
+
"teamId" in targetKey &&
|
|
297
|
+
parsed.teamId !== targetKey.teamId
|
|
298
|
+
)
|
|
299
|
+
return false;
|
|
300
|
+
if (
|
|
301
|
+
"departmentId" in parsed &&
|
|
302
|
+
"departmentId" in targetKey &&
|
|
303
|
+
parsed.departmentId !== targetKey.departmentId
|
|
304
|
+
)
|
|
305
|
+
return false;
|
|
306
|
+
if (
|
|
307
|
+
"chatId" in parsed &&
|
|
308
|
+
"chatId" in targetKey &&
|
|
309
|
+
parsed.chatId !== targetKey.chatId
|
|
310
|
+
)
|
|
311
|
+
return false;
|
|
312
|
+
if ("slug" in parsed && "slug" in targetKey && parsed.slug !== targetKey.slug)
|
|
313
|
+
return false;
|
|
314
|
+
if (
|
|
315
|
+
"unitId" in parsed &&
|
|
316
|
+
"unitId" in targetKey &&
|
|
317
|
+
parsed.unitId !== targetKey.unitId
|
|
318
|
+
)
|
|
319
|
+
return false;
|
|
320
|
+
if (
|
|
321
|
+
"scope" in parsed &&
|
|
322
|
+
"scope" in targetKey &&
|
|
323
|
+
parsed.scope !== targetKey.scope
|
|
324
|
+
)
|
|
325
|
+
return false;
|
|
271
326
|
|
|
272
327
|
return true;
|
|
273
328
|
}
|
package/src/resource-registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import type { Tier } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tier } from "./requests";
|
|
3
3
|
|
|
4
4
|
declare const ResourceKeyBrand: unique symbol;
|
|
5
5
|
declare const SectionKeyBrand: unique symbol;
|
|
@@ -9,15 +9,15 @@ export type ResourceKey = string & { readonly [ResourceKeyBrand]: true };
|
|
|
9
9
|
export type SectionKey = string & { readonly [SectionKeyBrand]: true };
|
|
10
10
|
export type Duration = number & { readonly [DurationBrand]: true };
|
|
11
11
|
|
|
12
|
-
const TIERS = [
|
|
12
|
+
const TIERS = ["P0", "P1", "P2", "P3"] as const satisfies readonly Tier[];
|
|
13
13
|
|
|
14
14
|
export const ResourceEntrySchema = z
|
|
15
15
|
.object({
|
|
16
16
|
resource: z.string().transform((s) => s as ResourceKey),
|
|
17
17
|
priority: z.enum(TIERS),
|
|
18
|
-
hydrationPhase: z.enum([
|
|
18
|
+
hydrationPhase: z.enum(["critical", "interactive", "background"]),
|
|
19
19
|
hydrationDepends: z.array(z.string().transform((s) => s as ResourceKey)),
|
|
20
|
-
mutationBehavior: z.enum([
|
|
20
|
+
mutationBehavior: z.enum(["collapse", "queue", "serial"]),
|
|
21
21
|
staleTimeMs: z
|
|
22
22
|
.number()
|
|
23
23
|
.int()
|
package/src/route-builder.ts
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* backend call site to the appropriate FastifyRequest/FastifyReply
|
|
12
12
|
* signature.
|
|
13
13
|
*/
|
|
14
|
-
import type { Tier } from
|
|
15
|
-
import type { ResourceKey } from
|
|
14
|
+
import type { Tier } from "./requests";
|
|
15
|
+
import type { ResourceKey } from "./resource-keys";
|
|
16
16
|
|
|
17
|
-
export type HttpMethod =
|
|
17
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
18
18
|
|
|
19
19
|
export type RouteDefinition<Handler = unknown> = {
|
|
20
20
|
readonly tier: Tier;
|
package/src/safe-mode.ts
CHANGED
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
* Canonical response header signalling safe-mode state to clients.
|
|
20
20
|
* Frontend imports this to avoid stringly-typed header lookups.
|
|
21
21
|
*/
|
|
22
|
-
export const SYSTEM_SAFE_MODE_HEADER =
|
|
22
|
+
export const SYSTEM_SAFE_MODE_HEADER = "X-System-Safe-Mode";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Values carried on the `X-System-Safe-Mode` header.
|
|
26
26
|
* `on` indicates the backend has entered safe-mode and is rejecting all
|
|
27
27
|
* non-P0 traffic with 503. `off` is the normal quiescent state.
|
|
28
28
|
*/
|
|
29
|
-
export type SafeModeValue =
|
|
29
|
+
export type SafeModeValue = "on" | "off";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Observable safe-mode state. Shared verbatim between backend (source of
|
package/src/security/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { Secret } from
|
|
2
|
-
export { wrapSecret, unwrapSecret } from
|
|
1
|
+
export type { Secret } from "./secret";
|
|
2
|
+
export { wrapSecret, unwrapSecret } from "./secret";
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
UsageClassSchema,
|
|
@@ -9,7 +9,7 @@ export {
|
|
|
9
9
|
CreateSecretRequestSchema,
|
|
10
10
|
RotateSecretRequestSchema,
|
|
11
11
|
DisableSecretRequestSchema,
|
|
12
|
-
} from
|
|
12
|
+
} from "./org-secrets";
|
|
13
13
|
export type {
|
|
14
14
|
UsageClass,
|
|
15
15
|
OrgSecretsAction,
|
|
@@ -18,4 +18,4 @@ export type {
|
|
|
18
18
|
CreateSecretRequest,
|
|
19
19
|
RotateSecretRequest,
|
|
20
20
|
DisableSecretRequest,
|
|
21
|
-
} from
|
|
21
|
+
} from "./org-secrets";
|
|
@@ -12,13 +12,17 @@
|
|
|
12
12
|
* Server is expected to wrap incoming `plaintext` in `Secret<T>` (see
|
|
13
13
|
* `./secret.ts`) before storage / encryption.
|
|
14
14
|
*/
|
|
15
|
-
import { z } from
|
|
15
|
+
import { z } from "zod";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Usage class of an org secret. Drives which resolver layer is allowed to read
|
|
19
19
|
* the value (AI provider keys, webhook signing secrets, generic credentials).
|
|
20
20
|
*/
|
|
21
|
-
export const UsageClassSchema = z.enum([
|
|
21
|
+
export const UsageClassSchema = z.enum([
|
|
22
|
+
"AI_PROVIDER",
|
|
23
|
+
"WEBHOOK_SIGNING",
|
|
24
|
+
"GENERIC",
|
|
25
|
+
]);
|
|
22
26
|
export type UsageClass = z.infer<typeof UsageClassSchema>;
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -27,13 +31,13 @@ export type UsageClass = z.infer<typeof UsageClassSchema>;
|
|
|
27
31
|
* signals (`access_denied`, `explicit_export`, `unusual_access`).
|
|
28
32
|
*/
|
|
29
33
|
export const OrgSecretsActionSchema = z.enum([
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
"created",
|
|
35
|
+
"rotated",
|
|
36
|
+
"disabled",
|
|
37
|
+
"enabled",
|
|
38
|
+
"access_denied",
|
|
39
|
+
"explicit_export",
|
|
40
|
+
"unusual_access",
|
|
37
41
|
]);
|
|
38
42
|
export type OrgSecretsAction = z.infer<typeof OrgSecretsActionSchema>;
|
|
39
43
|
|