@company-semantics/contracts 2.5.0 → 2.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -115,7 +115,7 @@
115
115
  "zod": "^4.4.3"
116
116
  },
117
117
  "devDependencies": {
118
- "@types/node": "^25.9.1",
118
+ "@types/node": "^22.19.19",
119
119
  "husky": "^9.1.7",
120
120
  "lint-staged": "^17.0.5",
121
121
  "markdownlint-cli2": "^0.22.1",
@@ -3234,7 +3234,7 @@ export interface components {
3234
3234
  level: "none" | "viewer" | "commenter" | "editor";
3235
3235
  reasons: {
3236
3236
  /** @enum {string} */
3237
- source: "org_rbac" | "sharing_policy" | "unit_baseline" | "acl_grant" | "doc_ownership";
3237
+ source: "org_rbac" | "sharing_policy" | "unit_baseline" | "unit_delegation" | "acl_grant" | "doc_ownership";
3238
3238
  detail: string;
3239
3239
  }[];
3240
3240
  canShare: boolean;
@@ -57,14 +57,19 @@ export const openApiRoutes = {
57
57
  '/api/org-units/{unitId}/ancestors': ['GET'],
58
58
  '/api/org-units/{unitId}/archive': ['POST'],
59
59
  '/api/org-units/{unitId}/children': ['GET'],
60
+ '/api/org-units/{unitId}/delegations': ['POST'],
61
+ '/api/org-units/{unitId}/delegations/{id}': ['DELETE', 'PATCH'],
60
62
  '/api/org-units/{unitId}/descendants': ['GET'],
61
63
  '/api/org-units/{unitId}/memberships': ['GET', 'POST'],
62
64
  '/api/org-units/{unitId}/memberships/{userId}': ['DELETE'],
63
65
  '/api/org-units/{unitId}/memberships/{userId}/role': ['PUT'],
66
+ '/api/org-units/{unitId}/owners': ['GET'],
64
67
  '/api/org-units/{unitId}/permissions': ['GET'],
65
68
  '/api/org-units/{unitId}/relationships': ['GET', 'POST'],
66
69
  '/api/org-units/{unitId}/reorder': ['POST'],
67
70
  '/api/org-units/{unitId}/reparent': ['POST'],
71
+ '/api/org-units/{unitId}/structural-leaders': ['POST'],
72
+ '/api/org-units/{unitId}/structural-leaders/{userId}': ['DELETE'],
68
73
  '/api/org/cancel-deletion': ['POST'],
69
74
  '/api/org/delete': ['POST'],
70
75
  '/api/org/deletion-eligibility': ['GET'],
@@ -75,6 +80,8 @@ export const openApiRoutes = {
75
80
  '/api/org/transfer-ownership/accept': ['POST'],
76
81
  '/api/org/transfer-ownership/preview': ['POST'],
77
82
  '/api/org/transfer-ownership/status': ['GET'],
83
+ '/api/orgs/{orgId}/admins': ['POST'],
84
+ '/api/orgs/{orgId}/admins/{userId}': ['DELETE'],
78
85
  '/api/orgs/{orgId}/ai-usage': ['GET'],
79
86
  '/api/orgs/{orgId}/billing': ['GET'],
80
87
  '/api/orgs/{orgId}/budget-config': ['GET', 'PUT'],
@@ -93,6 +100,8 @@ export const openApiRoutes = {
93
100
  '/api/user/profile': ['PATCH'],
94
101
  '/api/user/resync-slack-avatar': ['POST'],
95
102
  '/api/users/org-chart': ['GET'],
103
+ '/api/users/org-chart/import': ['POST'],
104
+ '/api/users/org-chart/import/{operationId}/retry': ['POST'],
96
105
  '/api/work-items/{id}': ['GET'],
97
106
  '/api/work-items/{id}/content': ['PUT'],
98
107
  '/api/work-items/{id}/title': ['PUT'],
package/src/index.ts CHANGED
@@ -663,7 +663,8 @@ export { openApiRoutes, type OpenApiRoute, type OpenApiMethod } from './generate
663
663
  export type { Secret } from './security/index'
664
664
  export { wrapSecret, unwrapSecret } from './security/index'
665
665
 
666
- // Org Secrets DTO schemas (PRD-00629) — masked-prefix-only, no value field
666
+ // Org secrets DTO schemas (PRD-00629)
667
+ // @see src/security/org-secrets.ts for invariants (no value field on summary)
667
668
  export {
668
669
  UsageClassSchema,
669
670
  OrgSecretsActionSchema,
@@ -672,7 +673,7 @@ export {
672
673
  CreateSecretRequestSchema,
673
674
  RotateSecretRequestSchema,
674
675
  DisableSecretRequestSchema,
675
- } from './security/org-secrets'
676
+ } from './security/index'
676
677
  export type {
677
678
  UsageClass,
678
679
  OrgSecretsAction,
@@ -681,7 +682,7 @@ export type {
681
682
  CreateSecretRequest,
682
683
  RotateSecretRequest,
683
684
  DisableSecretRequest,
684
- } from './security/org-secrets'
685
+ } from './security/index'
685
686
 
686
687
  // Analytics response metadata (shared vocabulary for OLTP/OLAP separation)
687
688
  // @see ADR-CTRL-053 for design rationale
@@ -742,3 +743,7 @@ export type {
742
743
  ControllerInput,
743
744
  ControllerOutput,
744
745
  } from './autotune'
746
+
747
+ // Meeting recorder vocabulary (PRD-00651)
748
+ // @see ./meetings/schemas.ts for invariants
749
+ export * from './meetings'
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Meeting recorder vocabulary barrel (PRD-00651).
3
+ *
4
+ * @see ./schemas.ts for the full schema definitions and invariants.
5
+ */
6
+ export {
7
+ RecordingIdSchema,
8
+ RecordingSourceSchema,
9
+ DetectedMeetingAppSchema,
10
+ TranscriptionProviderSchema,
11
+ RecordingStatusSchema,
12
+ RecordingQualitySchema,
13
+ MeetingVisibilitySchema,
14
+ SourceSegmentSchema,
15
+ TranscriptChunkSchema,
16
+ RecordingEventSchema,
17
+ TranscriptionSessionGrantSchema,
18
+ MeetingMetadataProjectionSchema,
19
+ } from './schemas';
20
+
21
+ export type {
22
+ RecordingId,
23
+ RecordingSource,
24
+ DetectedMeetingApp,
25
+ TranscriptionProvider,
26
+ RecordingStatus,
27
+ RecordingQuality,
28
+ MeetingVisibility,
29
+ SourceSegment,
30
+ TranscriptChunk,
31
+ RecordingEvent,
32
+ TranscriptionSessionGrant,
33
+ MeetingMetadataProjection,
34
+ } from './schemas';
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Meeting recorder shared vocabulary (PRD-00651).
3
+ *
4
+ * Zod schemas for the meeting capture / transcription pipeline. Consumed by
5
+ * the mac shell (capture runtime), backend (persistence + event projection),
6
+ * and app (UI surfaces). Lives in contracts because the boundary crosses
7
+ * three independent repos and breaking it would require coordinated releases
8
+ * (see CONTRACTS_POLICY.md promotion rule).
9
+ *
10
+ * INVARIANTS this vocabulary enforces:
11
+ * - RecordingId is the unified trace key (INV-MTG-7) — ULID, sortable,
12
+ * opaque to consumers.
13
+ * - The transcription grant is provider-agnostic (INV-MTG-15): the mac
14
+ * shell never sees a provider name; only `connectUrl` + `authHeaders` +
15
+ * audio config.
16
+ * - Visibility is meeting-scoped by default (`meeting_only`) and only
17
+ * widens via explicit policy decisions.
18
+ *
19
+ * Per the durable user preference `feedback_structured_metadata_not_json_strings`,
20
+ * descriptions live in `.meta({ description })` — structured metadata, not
21
+ * stringified JSON blobs on the schema.
22
+ */
23
+ import { z } from 'zod';
24
+
25
+ // =============================================================================
26
+ // Recording identity
27
+ // =============================================================================
28
+
29
+ /**
30
+ * ULID (Crockford base32, 26 chars). Used as the unified trace key spanning
31
+ * mac → backend → app per INV-MTG-7. Sortable by encoded timestamp prefix so
32
+ * recordings can be ordered without a separate `createdAt` column read.
33
+ */
34
+ export const RecordingIdSchema = z
35
+ .string()
36
+ .regex(/^[0-9A-HJKMNP-TV-Z]{26}$/, 'ULID required')
37
+ .meta({ description: 'ULID; unified trace key for the recording (INV-MTG-7).' });
38
+ export type RecordingId = z.infer<typeof RecordingIdSchema>;
39
+
40
+ // =============================================================================
41
+ // Enums (vocabulary)
42
+ // =============================================================================
43
+
44
+ /** Which capture stream a chunk came from. Two channels: mic (channel 0) and system (channel 1). */
45
+ export const RecordingSourceSchema = z.enum(['mic', 'system']).meta({
46
+ description: 'Capture stream identity. Mic = channel 0, system audio = channel 1.',
47
+ });
48
+ export type RecordingSource = z.infer<typeof RecordingSourceSchema>;
49
+
50
+ /**
51
+ * Which meeting app the system audio is coming from. `manual` covers the
52
+ * "user started a recording with no detected app" debug path. The
53
+ * `meet-browser` variant covers Google Meet running inside a browser tab,
54
+ * which the BrowserAudioDetector heuristic identifies (PRD-00655).
55
+ */
56
+ export const DetectedMeetingAppSchema = z
57
+ .enum(['zoom', 'teams', 'meet-browser', 'slack-huddle', 'manual'])
58
+ .meta({ description: 'Auto-detected meeting app that owns the system audio stream.' });
59
+ export type DetectedMeetingApp = z.infer<typeof DetectedMeetingAppSchema>;
60
+
61
+ /**
62
+ * Transcription provider class. The mac shell never sees this — it's a
63
+ * backend / app concern for routing + billing visibility (INV-MTG-15 ensures
64
+ * the mac side stays provider-opaque).
65
+ */
66
+ export const TranscriptionProviderSchema = z
67
+ .enum(['deepgram', 'assemblyai', 'whisper-local'])
68
+ .meta({ description: 'Server-side selection of transcription provider; never crosses to mac.' });
69
+ export type TranscriptionProvider = z.infer<typeof TranscriptionProviderSchema>;
70
+
71
+ /**
72
+ * Lifecycle states for a recording entity. `partial-transcript-network` is
73
+ * the explicit non-binary state for "audio captured but the transcript
74
+ * stream dropped mid-session" — keeping it distinct from `failed` lets the
75
+ * UI surface a remediation hint without losing the captured artifact.
76
+ */
77
+ export const RecordingStatusSchema = z
78
+ .enum([
79
+ 'draft',
80
+ 'recording',
81
+ 'processing',
82
+ 'finalized',
83
+ 'failed',
84
+ 'partial-transcript-network',
85
+ 'cancelled',
86
+ ])
87
+ .meta({ description: 'Coarse lifecycle state for a recording entity.' });
88
+ export type RecordingStatus = z.infer<typeof RecordingStatusSchema>;
89
+
90
+ /** Quality signal emitted by the capture runtime. Used to surface degradation in the UI. */
91
+ export const RecordingQualitySchema = z.enum(['clean', 'degraded']).meta({
92
+ description: 'Capture-runtime quality signal. `degraded` triggers a UI hint.',
93
+ });
94
+ export type RecordingQuality = z.infer<typeof RecordingQualitySchema>;
95
+
96
+ /**
97
+ * Visibility band for the finalized meeting projection. `meeting_only` is the
98
+ * default — captured material stays visible only to attendees of the meeting
99
+ * itself. `shared` opens it to anyone the owner explicitly shares with;
100
+ * `org` opens it to the org; `finalized_private` is the explicit "private
101
+ * even after finalization" lock per the participant-only constraint
102
+ * (`feedback_retrieval_default_off_for_noisy_sources`).
103
+ */
104
+ export const MeetingVisibilitySchema = z
105
+ .enum(['meeting_only', 'shared', 'org', 'finalized_private'])
106
+ .meta({
107
+ description:
108
+ 'Visibility band for the finalized meeting projection. Default `meeting_only`.',
109
+ });
110
+ export type MeetingVisibility = z.infer<typeof MeetingVisibilitySchema>;
111
+
112
+ // =============================================================================
113
+ // Source segments (the per-capture-stream view of a recording)
114
+ // =============================================================================
115
+
116
+ /**
117
+ * A continuous segment captured from a single stream within a recording.
118
+ * One recording has at least one segment per active source. New segments
119
+ * are emitted when the upstream signal pauses and resumes.
120
+ */
121
+ export const SourceSegmentSchema = z
122
+ .object({
123
+ recordingId: RecordingIdSchema,
124
+ source: RecordingSourceSchema,
125
+ sequence: z.number().int().nonnegative(),
126
+ startedAtMs: z.number().int().nonnegative(),
127
+ endedAtMs: z.number().int().nonnegative().nullable(),
128
+ sampleRateHz: z.literal(16000),
129
+ channels: z.literal(1),
130
+ })
131
+ .meta({ description: 'Per-source continuous capture segment within a recording.' });
132
+ export type SourceSegment = z.infer<typeof SourceSegmentSchema>;
133
+
134
+ // =============================================================================
135
+ // Transcript chunks (the per-window result of transcription)
136
+ // =============================================================================
137
+
138
+ /**
139
+ * A single transcript window from the provider. `final = true` means the
140
+ * provider has committed to this text; `false` is an interim hypothesis.
141
+ * The mac shell forwards both kinds — the UI is responsible for collapsing
142
+ * interim results on top of the latest commit.
143
+ */
144
+ export const TranscriptChunkSchema = z
145
+ .object({
146
+ recordingId: RecordingIdSchema,
147
+ source: RecordingSourceSchema,
148
+ sequence: z.number().int().nonnegative(),
149
+ startedAtMs: z.number().int().nonnegative(),
150
+ endedAtMs: z.number().int().nonnegative(),
151
+ text: z.string(),
152
+ final: z.boolean(),
153
+ speakerLabel: z.string().nullable().optional(),
154
+ confidence: z.number().min(0).max(1).optional(),
155
+ })
156
+ .meta({ description: 'A single transcript window (interim or final) from the provider.' });
157
+ export type TranscriptChunk = z.infer<typeof TranscriptChunkSchema>;
158
+
159
+ // =============================================================================
160
+ // Recording events (the event stream that drives backend persistence)
161
+ // =============================================================================
162
+
163
+ const SessionOpenedEventSchema = z.object({
164
+ kind: z.literal('session_opened'),
165
+ recordingId: RecordingIdSchema,
166
+ atMs: z.number().int().nonnegative(),
167
+ });
168
+
169
+ const ChunkBatchEventSchema = z.object({
170
+ kind: z.literal('chunk_batch'),
171
+ recordingId: RecordingIdSchema,
172
+ chunks: z.array(TranscriptChunkSchema).min(1),
173
+ });
174
+
175
+ const QualityDegradedEventSchema = z.object({
176
+ kind: z.literal('quality_degraded'),
177
+ recordingId: RecordingIdSchema,
178
+ reason: z.string().min(1),
179
+ atMs: z.number().int().nonnegative(),
180
+ });
181
+
182
+ const SessionStoppedEventSchema = z.object({
183
+ kind: z.literal('session_stopped'),
184
+ recordingId: RecordingIdSchema,
185
+ atMs: z.number().int().nonnegative(),
186
+ expectedLastSequence: z.number().int().nonnegative(),
187
+ });
188
+
189
+ /**
190
+ * Discriminated union of events emitted by the mac shell during a recording
191
+ * session. The backend consumes these and projects them onto the
192
+ * `MeetingMetadataProjection` row.
193
+ *
194
+ * Ordering invariant: `session_opened` is always the first event for a
195
+ * recordingId; `session_stopped` is always the last. `chunk_batch` and
196
+ * `quality_degraded` may interleave.
197
+ */
198
+ export const RecordingEventSchema = z
199
+ .discriminatedUnion('kind', [
200
+ SessionOpenedEventSchema,
201
+ ChunkBatchEventSchema,
202
+ QualityDegradedEventSchema,
203
+ SessionStoppedEventSchema,
204
+ ])
205
+ .meta({
206
+ description:
207
+ 'Event emitted by the capture runtime during a recording session (INV-MTG ordering).',
208
+ });
209
+ export type RecordingEvent = z.infer<typeof RecordingEventSchema>;
210
+
211
+ // =============================================================================
212
+ // Transcription session grant (provider-agnostic handshake)
213
+ // =============================================================================
214
+
215
+ /**
216
+ * Server-issued grant that authorizes the mac shell to open a WebSocket to
217
+ * the transcription provider. Per INV-MTG-15 the mac never sees a provider
218
+ * name — only `connectUrl`, `authHeaders`, and the audio config it must
219
+ * speak. `authHeaders` carries an ephemeral token; rotation is the server's
220
+ * job, and the grant has a short `expiresAt`.
221
+ */
222
+ export const TranscriptionSessionGrantSchema = z
223
+ .object({
224
+ sessionId: RecordingIdSchema,
225
+ connectUrl: z.string().url(),
226
+ authHeaders: z.record(z.string(), z.string()),
227
+ expiresAt: z.string().datetime(),
228
+ audioConfig: z.object({
229
+ sampleRateHz: z.literal(16000),
230
+ channels: z.literal(2),
231
+ encoding: z.literal('linear16'),
232
+ chunkMs: z.literal(200),
233
+ }),
234
+ capabilities: z.object({
235
+ interimResults: z.boolean(),
236
+ multichannelDiarization: z.boolean(),
237
+ reconnectionTokens: z.boolean(),
238
+ }),
239
+ })
240
+ .meta({
241
+ description:
242
+ 'Provider-agnostic grant the mac shell consumes. Mac never sees provider name (INV-MTG-15).',
243
+ });
244
+ export type TranscriptionSessionGrant = z.infer<typeof TranscriptionSessionGrantSchema>;
245
+
246
+ // =============================================================================
247
+ // Meeting metadata projection (finalized read model)
248
+ // =============================================================================
249
+
250
+ /**
251
+ * Finalized projection row for a completed recording. This is what the app
252
+ * reads for the meeting list / detail views. Drafts and in-flight recordings
253
+ * are excluded from this projection per
254
+ * `feedback_live_document_semantics` — indexers filter on `status` to
255
+ * suppress drafts from the search authority.
256
+ */
257
+ export const MeetingMetadataProjectionSchema = z
258
+ .object({
259
+ recordingId: RecordingIdSchema,
260
+ ownerUserId: z.string().uuid(),
261
+ orgId: z.string().uuid(),
262
+ title: z.string().nullable(),
263
+ detectedApp: DetectedMeetingAppSchema,
264
+ visibility: MeetingVisibilitySchema,
265
+ status: RecordingStatusSchema,
266
+ quality: RecordingQualitySchema,
267
+ startedAt: z.string().datetime(),
268
+ endedAt: z.string().datetime().nullable(),
269
+ durationMs: z.number().int().nonnegative(),
270
+ participantUserIds: z.array(z.string().uuid()),
271
+ transcriptAvailable: z.boolean(),
272
+ })
273
+ .meta({
274
+ description: 'Finalized read projection for a recording (excludes drafts).',
275
+ });
276
+ export type MeetingMetadataProjection = z.infer<typeof MeetingMetadataProjectionSchema>;
@@ -98,7 +98,7 @@ describe('OrgUnitMembershipSchema', () => {
98
98
  orgId: UUID_B,
99
99
  unitId: UUID_C,
100
100
  userId: UUID_A,
101
- membershipRole: 'manager',
101
+ membershipRole: 'l1_unit_owner',
102
102
  status: 'active',
103
103
  source: 'google_groups',
104
104
  sourceRef: 'eng@example.com',
@@ -226,7 +226,7 @@ describe('Enum exhaustiveness', () => {
226
226
  describe('OrgUnitPermissionsEntrySchema', () => {
227
227
  const entry = {
228
228
  userId: UUID_A,
229
- membershipRole: 'manager' as const,
229
+ membershipRole: 'l1_unit_owner' as const,
230
230
  inheritedFromUnitId: UUID_C,
231
231
  inheritedFromUnitName: 'Engineering',
232
232
  };
@@ -1,27 +1,30 @@
1
1
  /**
2
- * Org Secrets DTO schemas
2
+ * Org Secrets DTO schemas (PRD-00629).
3
3
  *
4
- * Zod schemas for the org_secrets surface (PRD-00629). The contracts package
5
- * defines what admin clients may observe and submit. The decrypted credential
6
- * value is intentionally absent from every surface here the only
7
- * credential-derived field exposed is `maskedPrefix`.
4
+ * Wire shapes for the `org_secrets` admin surface. The decrypted credential
5
+ * value never crosses this boundary `OrgSecretSummarySchema` exposes only a
6
+ * masked prefix derived from the plaintext (e.g. the first few characters of
7
+ * `sk-...`), never the value itself. Plaintext travels INTO the server through
8
+ * `CreateSecretRequestSchema` / `RotateSecretRequestSchema` via
9
+ * `SecretValueStringSchema`, which is a branded bounded string so accidental
10
+ * propagation through logs / template literals is obvious in code review.
8
11
  *
9
- * Servers receiving plaintext (CreateSecretRequest / RotateSecretRequest)
10
- * are expected to wrap it with {@link Secret} from `./secret` before storage,
11
- * and never re-emit it to clients.
12
+ * Server is expected to wrap incoming `plaintext` in `Secret<T>` (see
13
+ * `./secret.ts`) before storage / encryption.
12
14
  */
13
15
  import { z } from 'zod';
14
16
 
15
17
  /**
16
- * Classification of how a credential is consumed at runtime. Drives downstream
17
- * authorization and audit categorization.
18
+ * Usage class of an org secret. Drives which resolver layer is allowed to read
19
+ * the value (AI provider keys, webhook signing secrets, generic credentials).
18
20
  */
19
21
  export const UsageClassSchema = z.enum(['AI_PROVIDER', 'WEBHOOK_SIGNING', 'GENERIC']);
20
22
  export type UsageClass = z.infer<typeof UsageClassSchema>;
21
23
 
22
24
  /**
23
- * Auditable lifecycle and access events for an org secret. Used by the audit
24
- * trail and observability surfaces; the schema is the closed enumeration.
25
+ * Audit-log action vocabulary for `org_secrets_audit_log`. Includes both
26
+ * lifecycle events (`created`, `rotated`, `disabled`, `enabled`) and access
27
+ * signals (`access_denied`, `explicit_export`, `unusual_access`).
25
28
  */
26
29
  export const OrgSecretsActionSchema = z.enum([
27
30
  'created',
@@ -34,30 +37,34 @@ export const OrgSecretsActionSchema = z.enum([
34
37
  ]);
35
38
  export type OrgSecretsAction = z.infer<typeof OrgSecretsActionSchema>;
36
39
 
40
+ declare const SecretValueStringBrand: unique symbol;
41
+
37
42
  /**
38
- * Branded plaintext credential string. Bounded so accidental logs do not leak
39
- * megabytes; branded to discourage propagation through generic string flows.
40
- * Servers must wrap with Secret<T> before storage and never echo it back.
43
+ * Plaintext credential string. Bounded (min 1, max 8192 chars) so accidental
44
+ * logs cannot leak megabytes, and branded so propagation outside the secrets
45
+ * domain is visible at the type level. Server MUST wrap in `Secret<T>` before
46
+ * storage so the multi-surface redaction protection applies.
41
47
  */
42
48
  export const SecretValueStringSchema = z
43
49
  .string()
44
50
  .min(1)
45
51
  .max(8192)
46
- .brand<'SecretValueString'>();
52
+ .brand<typeof SecretValueStringBrand>();
47
53
  export type SecretValueString = z.infer<typeof SecretValueStringSchema>;
48
54
 
49
55
  /**
50
- * Admin-visible summary of an `org_secrets` row. The decrypted value is
51
- * intentionally absent — `maskedPrefix` is the only credential-derived field
52
- * exposed by this surface (INV-PRD-00629: no value in summary).
56
+ * Admin-facing summary of an `org_secrets` row.
53
57
  *
54
- * `usageCount` is serialized as a string for cross-driver bigint safety.
58
+ * INVARIANT: this schema MUST NOT contain the decrypted `value`. The only
59
+ * credential-derived field exposed is `maskedPrefix`, a short, lossy preview.
60
+ * Adding a `value` field here would defeat the purpose of the whole subsystem;
61
+ * the secrets API never decrypts to clients.
55
62
  */
56
63
  export const OrgSecretSummarySchema = z.object({
57
64
  id: z.string().uuid(),
58
65
  orgId: z.string().uuid().nullable(),
59
66
  usageClass: UsageClassSchema,
60
- secretName: z.string().min(1).max(128),
67
+ secretName: z.string().min(1),
61
68
  maskedPrefix: z.string().min(1).max(64),
62
69
  version: z.number().int().positive(),
63
70
  enabled: z.boolean(),
@@ -69,8 +76,9 @@ export const OrgSecretSummarySchema = z.object({
69
76
  export type OrgSecretSummary = z.infer<typeof OrgSecretSummarySchema>;
70
77
 
71
78
  /**
72
- * Request to create a new org secret. Server-side handlers must wrap
73
- * `plaintext` in Secret<T> immediately on receipt.
79
+ * Request body for `POST /api/admin/secrets`. `orgId` is nullable so global
80
+ * (platform-wide) secrets are expressible. `plaintext` is the only field that
81
+ * carries credential material; everything else is metadata.
74
82
  */
75
83
  export const CreateSecretRequestSchema = z.object({
76
84
  orgId: z.string().uuid().nullable(),
@@ -82,7 +90,9 @@ export const CreateSecretRequestSchema = z.object({
82
90
  export type CreateSecretRequest = z.infer<typeof CreateSecretRequestSchema>;
83
91
 
84
92
  /**
85
- * Request to rotate an existing secret to a new plaintext value.
93
+ * Request body for `POST /api/admin/secrets/:id/rotate`. Re-supplies the
94
+ * plaintext under a new version; the previous version is retained per the
95
+ * rotation policy documented in the schema migration.
86
96
  */
87
97
  export const RotateSecretRequestSchema = z.object({
88
98
  newPlaintext: SecretValueStringSchema,
@@ -91,8 +101,9 @@ export const RotateSecretRequestSchema = z.object({
91
101
  export type RotateSecretRequest = z.infer<typeof RotateSecretRequestSchema>;
92
102
 
93
103
  /**
94
- * Request to disable an existing secret. A non-empty `reason` is required so
95
- * the audit trail always captures why a credential was taken out of service.
104
+ * Request body for `POST /api/admin/secrets/:id/disable`. A non-empty reason
105
+ * is required so the audit trail records WHY the secret was disabled, not just
106
+ * that it was.
96
107
  */
97
108
  export const DisableSecretRequestSchema = z.object({
98
109
  reason: z.string().min(1).max(512),