@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.
Files changed (141) hide show
  1. package/package.json +4 -1
  2. package/src/__tests__/resource-keys.test.ts +30 -23
  3. package/src/admin/authz-simulate.ts +4 -4
  4. package/src/admin/direct-grants.ts +2 -2
  5. package/src/api/generated-spec-hash.ts +2 -2
  6. package/src/api/generated.ts +97 -0
  7. package/src/api/http/routes/ai-chat.ts +3 -3
  8. package/src/api/http/utils/resource-response.ts +5 -2
  9. package/src/api/index.ts +4 -4
  10. package/src/api/primitives.ts +6 -2
  11. package/src/auth/README.md +1 -0
  12. package/src/auth/index.ts +12 -5
  13. package/src/autotune.ts +5 -1
  14. package/src/billing/index.ts +1 -1
  15. package/src/billing/types.ts +1 -1
  16. package/src/chat/README.md +3 -0
  17. package/src/chat/__tests__/runtime-profile.test.ts +68 -48
  18. package/src/chat/index.ts +10 -4
  19. package/src/chat/runtime-profile.ts +25 -10
  20. package/src/chat/schemas.ts +49 -41
  21. package/src/chat/types.ts +48 -42
  22. package/src/ci-envelope/README.md +2 -0
  23. package/src/ci-envelope/__tests__/transitions.test.ts +56 -56
  24. package/src/ci-envelope/index.ts +2 -2
  25. package/src/ci-envelope/types.ts +20 -20
  26. package/src/ci-results/index.ts +2 -2
  27. package/src/ci-results/repo-ci-result.ts +15 -12
  28. package/src/compatibility.ts +6 -6
  29. package/src/content/index.ts +10 -4
  30. package/src/content/schemas.ts +42 -24
  31. package/src/dispatch/index.ts +18 -15
  32. package/src/email/__tests__/registry.test.ts +81 -77
  33. package/src/email/index.ts +3 -3
  34. package/src/email/registry.ts +25 -25
  35. package/src/email/types.ts +43 -43
  36. package/src/errors/index.ts +8 -8
  37. package/src/execution/__tests__/events.test.ts +42 -42
  38. package/src/execution/__tests__/lifecycle.test.ts +192 -190
  39. package/src/execution/__tests__/registry.test.ts +114 -114
  40. package/src/execution/audit-export.ts +4 -4
  41. package/src/execution/errors.ts +7 -7
  42. package/src/execution/event-metadata.ts +4 -4
  43. package/src/execution/events.ts +23 -21
  44. package/src/execution/expiry.ts +5 -5
  45. package/src/execution/hash-chain.ts +2 -2
  46. package/src/execution/index.ts +19 -28
  47. package/src/execution/kinds.ts +7 -7
  48. package/src/execution/lifecycle.ts +33 -33
  49. package/src/execution/registry.ts +63 -63
  50. package/src/execution/schemas.ts +31 -23
  51. package/src/execution/status.ts +45 -26
  52. package/src/execution/summary.ts +16 -17
  53. package/src/execution/timeline-ui.ts +9 -9
  54. package/src/execution/types.ts +31 -25
  55. package/src/generated/openapi-routes.ts +1 -0
  56. package/src/guards/config.ts +22 -18
  57. package/src/guards/index.ts +4 -4
  58. package/src/guards/types.ts +32 -24
  59. package/src/identity/__tests__/avatar.test.ts +68 -59
  60. package/src/identity/avatar.ts +8 -8
  61. package/src/identity/display-name.ts +3 -3
  62. package/src/identity/index.ts +8 -8
  63. package/src/identity/people-org-chart.ts +8 -4
  64. package/src/identity/schemas.ts +28 -18
  65. package/src/identity/types.ts +5 -5
  66. package/src/impersonation/index.ts +5 -5
  67. package/src/impersonation/schemas.ts +15 -9
  68. package/src/impersonation-events.ts +21 -21
  69. package/src/impersonation.ts +25 -24
  70. package/src/index.ts +117 -90
  71. package/src/interfaces/mcp/tools/help.ts +19 -19
  72. package/src/internal-admin.ts +6 -6
  73. package/src/mcp/README.md +2 -0
  74. package/src/mcp/__tests__/capability-graph.test.ts +290 -290
  75. package/src/mcp/capability-graph.ts +42 -40
  76. package/src/mcp/failure-context.ts +1 -3
  77. package/src/mcp/index.ts +57 -57
  78. package/src/mcp/resources.ts +9 -9
  79. package/src/meetings/index.ts +2 -2
  80. package/src/meetings/schemas.ts +51 -34
  81. package/src/message-parts/README.md +2 -0
  82. package/src/message-parts/__tests__/builder.test.ts +142 -142
  83. package/src/message-parts/__tests__/confirmation.test.ts +100 -86
  84. package/src/message-parts/__tests__/preview.test.ts +63 -63
  85. package/src/message-parts/__tests__/wire.test.ts +130 -124
  86. package/src/message-parts/builder.ts +23 -23
  87. package/src/message-parts/confirmation.ts +17 -14
  88. package/src/message-parts/execution.ts +7 -7
  89. package/src/message-parts/index.ts +10 -10
  90. package/src/message-parts/lifecycle.ts +25 -25
  91. package/src/message-parts/preview.ts +30 -30
  92. package/src/message-parts/types.ts +27 -27
  93. package/src/message-parts/wire.ts +24 -24
  94. package/src/mutations.ts +2 -2
  95. package/src/observability.ts +23 -11
  96. package/src/org/__tests__/org-units.test.ts +131 -96
  97. package/src/org/__tests__/tree-ordering.test.ts +57 -37
  98. package/src/org/__tests__/view-scopes.test.ts +40 -40
  99. package/src/org/domain.ts +9 -9
  100. package/src/org/index.ts +24 -21
  101. package/src/org/org-units.ts +34 -20
  102. package/src/org/schemas.ts +201 -127
  103. package/src/org/sharing.ts +17 -13
  104. package/src/org/tree-ordering.ts +3 -1
  105. package/src/org/types.ts +54 -47
  106. package/src/org/view-scopes.ts +9 -9
  107. package/src/permissions/access-levels.ts +7 -2
  108. package/src/permissions/access-source.ts +6 -6
  109. package/src/permissions/index.ts +5 -5
  110. package/src/permissions/orgchart-roles.ts +7 -7
  111. package/src/permissions/permission-introspection.ts +7 -5
  112. package/src/permissions/share-api.ts +19 -9
  113. package/src/pressure.ts +4 -4
  114. package/src/queryIntent.ts +21 -21
  115. package/src/ralph/__tests__/prd-groups.test.ts +159 -159
  116. package/src/ralph/__tests__/prd.test.ts +30 -30
  117. package/src/ralph/index.ts +3 -8
  118. package/src/ralph/prd.ts +33 -33
  119. package/src/ralph/progress.ts +1 -1
  120. package/src/rate-limit/README.md +4 -4
  121. package/src/rate-limit/index.ts +3 -3
  122. package/src/requests.ts +36 -8
  123. package/src/resource-keys.ts +191 -136
  124. package/src/resource-registry.ts +5 -5
  125. package/src/route-builder.ts +3 -3
  126. package/src/safe-mode.ts +2 -2
  127. package/src/security/index.ts +4 -4
  128. package/src/security/org-secrets.ts +13 -9
  129. package/src/security/secret.ts +3 -3
  130. package/src/sse.ts +3 -1
  131. package/src/system/README.md +3 -0
  132. package/src/system/capabilities.ts +22 -23
  133. package/src/system/diagram.ts +45 -45
  134. package/src/system/index.ts +14 -14
  135. package/src/tiers.ts +1 -1
  136. package/src/timeouts.ts +1 -1
  137. package/src/tracing.ts +30 -30
  138. package/src/types/analytics.ts +2 -2
  139. package/src/usage/README.md +3 -0
  140. package/src/usage/execution-types.ts +69 -69
  141. package/src/usage/types.ts +7 -3
@@ -45,7 +45,7 @@ export type RalphIteration = {
45
45
  /**
46
46
  * Ralph operation mode.
47
47
  */
48
- export type RalphMode = 'hitl' | 'afk';
48
+ export type RalphMode = "hitl" | "afk";
49
49
 
50
50
  /**
51
51
  * Complete progress state for a Ralph session.
@@ -18,8 +18,8 @@ Configuration for rate limit windows:
18
18
 
19
19
  ```typescript
20
20
  interface RateLimitConfig {
21
- maxRequests: number; // Maximum requests allowed in window
22
- windowMs: number; // Window duration in milliseconds
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; // true = permitted, false = rate limited
32
+ allowed: boolean; // true = permitted, false = rate limited
33
33
  remaining: number; // Requests remaining in current window
34
- resetAt: number; // Unix timestamp when window resets
34
+ resetAt: number; // Unix timestamp when window resets
35
35
  }
36
36
  ```
37
37
 
@@ -5,9 +5,9 @@
5
5
  */
6
6
 
7
7
  export enum RateLimitTier {
8
- FREE = 'free',
9
- PRO = 'pro',
10
- ENTERPRISE = 'enterprise',
8
+ FREE = "free",
9
+ PRO = "pro",
10
+ ENTERPRISE = "enterprise",
11
11
  }
12
12
 
13
13
  export interface RateLimitConfig {
package/src/requests.ts CHANGED
@@ -1,6 +1,6 @@
1
- export type Tier = 'P0' | 'P1' | 'P2' | 'P3';
1
+ export type Tier = "P0" | "P1" | "P2" | "P3";
2
2
 
3
- export type MutationBehavior = 'collapse' | 'queue' | 'parallel';
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: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
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
- | { type: 'enqueued'; id: string; descriptor: RequestDescriptor; queuedAt: number }
25
- | { type: 'started'; id: string; descriptor: RequestDescriptor; startedAt: number; waitedMs: number }
26
- | { type: 'settled'; id: string; descriptor: RequestDescriptor; durationMs: number; ok: boolean; status?: number }
27
- | { type: 'aborted'; id: string; descriptor: RequestDescriptor; reason: 'superseded' | 'invalidated' | 'unmounted' | 'external' }
28
- | { type: 'deduped'; id: string; descriptor: RequestDescriptor; sharedWith: string };
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
+ };
@@ -5,52 +5,56 @@
5
5
  */
6
6
  export type ResourceKey =
7
7
  // Collections (plural) — lists of entities
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 }
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: '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 }
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: '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 }
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: 'orgUnitOwners'; orgId: string }
41
+ | { type: "orgUnitOwners"; orgId: string }
42
42
  // People reporting (ADR-BE-166) — drives the settings Org chart drill-down
43
- | { type: 'peopleOrgChart'; orgId: string }
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: 'internalAdminAiProviders'; scope: 'system' }
47
- | { type: 'internalAdminPrompts'; scope: 'system' }
48
- | { type: 'internalAdminAiRuntimeDefaults'; scope: 'system' }
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: 'dismissedBanners'; userId: string }
51
- | { type: 'userOrgs'; userId: string }
52
- | { type: 'sessions'; userId: string }
53
- | { type: 'viewer'; userId: string };
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
- 'members', 'departments', 'chats', 'teams', 'integrations', 'invites',
73
- 'auditEvents', 'timeline', 'workspace', 'workspaceDomains', 'authSettings',
74
- 'billing', 'aiUsage', 'deletionEligibility', 'transferOwnership', 'companyMdDocs',
75
- 'orgTree', 'orgLevelConfig', 'peopleOrgChart', 'orgUnitOwners',
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 = ['dismissedBanners', 'userOrgs', 'sessions', 'viewer'] as const;
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
- 'internalAdminAiProviders', 'internalAdminPrompts', 'internalAdminAiRuntimeDefaults',
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 'member':
130
+ case "member":
102
131
  return [key.type, key.orgId, key.memberId] as const;
103
- case 'team':
132
+ case "team":
104
133
  return [key.type, key.orgId, key.teamId] as const;
105
- case 'department':
134
+ case "department":
106
135
  return [key.type, key.orgId, key.departmentId] as const;
107
- case 'chat':
136
+ case "chat":
108
137
  return [key.type, key.orgId, key.chatId] as const;
109
- case 'companyMdDoc':
110
- case 'companyMdContextBank':
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 'orgUnit':
115
- case 'orgUnitChildren':
116
- case 'orgUnitAncestors':
117
- case 'orgUnitMemberships':
118
- case 'orgUnitPermissions':
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 'dismissedBanners':
123
- case 'userOrgs':
124
- case 'sessions':
125
- case 'viewer':
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 'members':
130
- case 'departments':
131
- case 'chats':
132
- case 'teams':
133
- case 'integrations':
134
- case 'invites':
135
- case 'auditEvents':
136
- case 'timeline':
137
- case 'workspace':
138
- case 'workspaceDomains':
139
- case 'authSettings':
140
- case 'billing':
141
- case 'aiUsage':
142
- case 'deletionEligibility':
143
- case 'transferOwnership':
144
- case 'companyMdDocs':
145
- case 'orgTree':
146
- case 'orgLevelConfig':
147
- case 'peopleOrgChart':
148
- case 'orgUnitOwners':
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 'internalAdminAiProviders':
153
- case 'internalAdminPrompts':
154
- case 'internalAdminAiRuntimeDefaults':
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(`Invalid query key: expected at least [type, scope], got ${JSON.stringify(queryKey)}`);
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: 'memberId',
178
- team: 'teamId',
179
- department: 'departmentId',
180
- chat: 'chatId',
181
- companyMdDoc: 'slug',
182
- companyMdContextBank: 'slug',
183
- orgUnit: 'unitId',
184
- orgUnitChildren: 'unitId',
185
- orgUnitAncestors: 'unitId',
186
- orgUnitMemberships: 'unitId',
187
- orgUnitPermissions: 'unitId',
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(`Invalid query key for '${type}': expected [type, orgId, ${identityFields[type]}], got ${JSON.stringify(queryKey)}`);
225
+ throw new Error(
226
+ `Invalid query key for '${type}': expected [type, orgId, ${identityFields[type]}], got ${JSON.stringify(queryKey)}`,
227
+ );
193
228
  }
194
- return { type, orgId: rest[0], [identityFields[type]]: rest[1] } as ResourceKey;
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: 'system' } as ResourceKey;
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(queryKey: readonly unknown[], targetKey: ResourceKey): boolean {
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 === 'string');
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 ('orgId' in parsed && 'orgId' in targetKey && parsed.orgId !== targetKey.orgId) return false;
263
- if ('userId' in parsed && 'userId' in targetKey && parsed.userId !== targetKey.userId) return false;
264
- if ('memberId' in parsed && 'memberId' in targetKey && parsed.memberId !== targetKey.memberId) return false;
265
- if ('teamId' in parsed && 'teamId' in targetKey && parsed.teamId !== targetKey.teamId) return false;
266
- if ('departmentId' in parsed && 'departmentId' in targetKey && parsed.departmentId !== targetKey.departmentId) return false;
267
- if ('chatId' in parsed && 'chatId' in targetKey && parsed.chatId !== targetKey.chatId) return false;
268
- if ('slug' in parsed && 'slug' in targetKey && parsed.slug !== targetKey.slug) return false;
269
- if ('unitId' in parsed && 'unitId' in targetKey && parsed.unitId !== targetKey.unitId) return false;
270
- if ('scope' in parsed && 'scope' in targetKey && parsed.scope !== targetKey.scope) return false;
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
  }
@@ -1,5 +1,5 @@
1
- import { z } from 'zod';
2
- import type { Tier } from './requests';
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 = ['P0', 'P1', 'P2', 'P3'] as const satisfies readonly Tier[];
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(['critical', 'interactive', 'background']),
18
+ hydrationPhase: z.enum(["critical", "interactive", "background"]),
19
19
  hydrationDepends: z.array(z.string().transform((s) => s as ResourceKey)),
20
- mutationBehavior: z.enum(['collapse', 'queue', 'serial']),
20
+ mutationBehavior: z.enum(["collapse", "queue", "serial"]),
21
21
  staleTimeMs: z
22
22
  .number()
23
23
  .int()
@@ -11,10 +11,10 @@
11
11
  * backend call site to the appropriate FastifyRequest/FastifyReply
12
12
  * signature.
13
13
  */
14
- import type { Tier } from './requests';
15
- import type { ResourceKey } from './resource-keys';
14
+ import type { Tier } from "./requests";
15
+ import type { ResourceKey } from "./resource-keys";
16
16
 
17
- export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
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 = 'X-System-Safe-Mode';
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 = 'on' | 'off';
29
+ export type SafeModeValue = "on" | "off";
30
30
 
31
31
  /**
32
32
  * Observable safe-mode state. Shared verbatim between backend (source of
@@ -1,5 +1,5 @@
1
- export type { Secret } from './secret';
2
- export { wrapSecret, unwrapSecret } from './secret';
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 './org-secrets';
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 './org-secrets';
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 'zod';
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(['AI_PROVIDER', 'WEBHOOK_SIGNING', 'GENERIC']);
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
- 'created',
31
- 'rotated',
32
- 'disabled',
33
- 'enabled',
34
- 'access_denied',
35
- 'explicit_export',
36
- 'unusual_access',
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