@thinkai/tai-api-contract 2.182.0 → 2.183.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.
@@ -1,7 +1,7 @@
1
1
  openapi: 3.0.3
2
2
  info:
3
3
  title: ThinkAI API
4
- version: 2.182.0
4
+ version: 2.183.0
5
5
  description: >
6
6
  Contract surface for the AI Driven SDLC backend used by ThinkAI.
7
7
  Workspace-scoped routes use `/workspaces/{workspaceId}/...`.
@@ -65,6 +65,8 @@ tags:
65
65
  AI-tool usage/spend, org chart, and readiness — never mock payloads.
66
66
  - name: WorkspaceActivity
67
67
  description: Workspace-wide lifecycle activity log — repo add/remove/re-add and integration lifecycle events (issue #515).
68
+ - name: AssessmentReports
69
+ description: Encrypted stored assessment buyer PDFs, secure link delivery, and hide/restore (issue #486).
68
70
  - name: PlatformAdmin
69
71
  description: Platform-admin operations restricted to PLATFORM_ADMIN_EMAILS. Callers must present a valid JWT from an email on that allowlist; all other callers receive 403.
70
72
  - name: HRIS
@@ -430,7 +432,10 @@ paths:
430
432
  are ignored, so an unscanned source cannot be marked fresh. The
431
433
  five-business-day clock uses Monday–Friday in America/New_York and is
432
434
  stored on the first passing result. A later blocker does not clear it.
433
- Active-engineer counts stay unloaded until stored activity can be counted.
435
+ The active-engineer count is loaded from stored commit authors and merged
436
+ pull requests in the 90-day baseline window (bots and identities not on the
437
+ org chart excluded). It stays unloaded until commit ingest covers the whole
438
+ window for every non-archived repository.
434
439
 
435
440
  **Authorization:** Bearer JWT required. Caller email must appear in `PLATFORM_ADMIN_EMAILS`.
436
441
  parameters:
@@ -602,7 +607,16 @@ paths:
602
607
  items:
603
608
  type: object
604
609
  required:
605
- [workspaceId, offerId, ready, blocker, nextAction, dueDate, evaluatedAt]
610
+ [
611
+ workspaceId,
612
+ offerId,
613
+ ready,
614
+ blocker,
615
+ nextAction,
616
+ clockStartedOn,
617
+ dueDate,
618
+ evaluatedAt,
619
+ ]
606
620
  properties:
607
621
  workspaceId:
608
622
  type: string
@@ -625,6 +639,11 @@ paths:
625
639
  href:
626
640
  type: string
627
641
  nullable: true
642
+ clockStartedOn:
643
+ type: string
644
+ nullable: true
645
+ format: date
646
+ description: America/New_York day the five-business-day clock started.
628
647
  dueDate:
629
648
  type: string
630
649
  nullable: true
@@ -668,174 +687,14 @@ paths:
668
687
  content:
669
688
  application/json:
670
689
  schema:
671
- type: object
672
- required:
673
- - manifest
674
- - workspaceId
675
- - expectedWorkspaceId
676
- - expectedRepositoryIds
677
- - soldWindowComplete
678
- - premiumAiAssessable
679
- - namedPersonSectionIds
680
- - tolerances
681
- properties:
682
- manifest:
683
- type: object
684
- required:
685
- - offerId
686
- - dictionaryVersion
687
- - validation
688
- - shareMode
689
- - watermark
690
- - scope
691
- - windows
692
- - sources
693
- - consent
694
- - sections
695
- properties:
696
- offerId:
697
- type: string
698
- enum: [engineering_readiness_baseline, agentic_ai_roi]
699
- dictionaryVersion:
700
- type: string
701
- validation:
702
- type: string
703
- enum: [validated, not_validated]
704
- shareMode:
705
- type: string
706
- enum: [internal, external]
707
- watermark:
708
- type: string
709
- nullable: true
710
- enum: ["NOT VALIDATED", "SAMPLE · SYNTHETIC DATA"]
711
- scope:
712
- type: object
713
- required: [repositories, excluded]
714
- properties:
715
- repositories:
716
- type: array
717
- items:
718
- type: object
719
- required: [id, name]
720
- properties:
721
- id: { type: string }
722
- name: { type: string }
723
- excluded:
724
- type: array
725
- items:
726
- type: object
727
- required: [id, name, reason]
728
- properties:
729
- id: { type: string }
730
- name: { type: string }
731
- reason: { type: string }
732
- windows:
733
- type: object
734
- required: [pre, post, transitionDays]
735
- properties:
736
- pre:
737
- type: object
738
- nullable: true
739
- required: [start, end]
740
- properties:
741
- start: { type: string }
742
- end: { type: string }
743
- post:
744
- type: object
745
- nullable: true
746
- required: [start, end]
747
- properties:
748
- start: { type: string }
749
- end: { type: string }
750
- transitionDays:
751
- type: integer
752
- sources:
753
- type: array
754
- items:
755
- type: object
756
- required: [provider, status, freshAt]
757
- properties:
758
- provider: { type: string }
759
- status: { type: string }
760
- freshAt: { type: string, nullable: true }
761
- consent:
762
- type: object
763
- required: [aggregateUse, recordedAt]
764
- properties:
765
- aggregateUse:
766
- type: string
767
- enum: [opt_in, opt_out, unset]
768
- recordedAt:
769
- type: string
770
- nullable: true
771
- sections:
772
- type: array
773
- items:
774
- type: object
775
- required: [id, status]
776
- properties:
777
- id: { type: string }
778
- status:
779
- type: string
780
- enum: [ok, failed, omitted]
781
- reason: { type: string }
782
- workspaceId:
783
- type: string
784
- expectedWorkspaceId:
785
- type: string
786
- expectedRepositoryIds:
787
- type: array
788
- items:
789
- type: string
790
- soldWindowComplete:
791
- type: boolean
792
- premiumAiAssessable:
793
- type: boolean
794
- namedPersonSectionIds:
795
- type: array
796
- items:
797
- type: string
798
- tolerances:
799
- type: array
800
- items:
801
- type: object
802
- required: [id, status]
803
- properties:
804
- id:
805
- type: string
806
- status:
807
- type: string
808
- enum: [unset, exceeded, within]
690
+ $ref: "#/components/schemas/AssessmentManifestValidationBodyDto"
809
691
  responses:
810
692
  "200":
811
693
  description: Validation result. Client validation and watermark inputs are ignored.
812
694
  content:
813
695
  application/json:
814
696
  schema:
815
- type: object
816
- required: [buyerDeliverable, validation, watermark, reasons, contentHash]
817
- properties:
818
- buyerDeliverable:
819
- type: boolean
820
- validation:
821
- type: string
822
- enum: [validated, not_validated]
823
- watermark:
824
- type: string
825
- nullable: true
826
- enum: ["NOT VALIDATED"]
827
- reasons:
828
- type: array
829
- items:
830
- type: object
831
- required: [code, message]
832
- properties:
833
- code:
834
- type: string
835
- message:
836
- type: string
837
- contentHash:
838
- type: string
697
+ $ref: "#/components/schemas/AssessmentManifestValidationResultDto"
839
698
  "400":
840
699
  description: Body failed validation.
841
700
  content:
@@ -861,6 +720,180 @@ paths:
861
720
  error: Forbidden
862
721
  code: forbidden
863
722
 
723
+ /workspaces/{workspaceId}/assessment-manifest/validate:
724
+ post:
725
+ tags: [Workspace]
726
+ summary: Validate this workspace's assessment manifest
727
+ operationId: validateWorkspaceAssessmentManifest
728
+ description: >
729
+ Runs the same fail-closed manifest check as `runAssessmentManifestValidation`
730
+ for the workspace in the path. The server ignores `manifest.validation` and
731
+ `manifest.watermark` from the client. `buyerDeliverable` is true only when
732
+ the result is validated and `shareMode` is internal. An external share-safe
733
+ result can be validated and is never the buyer file. Unset or exceeded
734
+ tolerances fail. No numeric tolerance is assumed. GitLab and Bitbucket
735
+ fixtures are not part of this operation.
736
+
737
+ `workspaceId` and `expectedWorkspaceId` in the body must equal the path
738
+ workspace. A body that names another workspace is rejected. Comparing two
739
+ workspaces stays on `runAssessmentManifestValidation`.
740
+
741
+ **Authorization:** Bearer JWT required. Caller must be a workspace admin,
742
+ or a platform admin who is a member of this workspace. Members who are not
743
+ workspace admins receive 403. Non-members of an existing workspace receive
744
+ 403. Unknown workspaces receive 404.
745
+ parameters:
746
+ - $ref: "#/components/parameters/WorkspaceId"
747
+ requestBody:
748
+ required: true
749
+ content:
750
+ application/json:
751
+ schema:
752
+ $ref: "#/components/schemas/AssessmentManifestValidationBodyDto"
753
+ responses:
754
+ "200":
755
+ description: Validation result. Client validation and watermark inputs are ignored.
756
+ content:
757
+ application/json:
758
+ schema:
759
+ $ref: "#/components/schemas/AssessmentManifestValidationResultDto"
760
+ "400":
761
+ description: Body failed validation.
762
+ content:
763
+ application/json:
764
+ schema:
765
+ $ref: "#/components/schemas/ErrorMessageDto"
766
+ example:
767
+ error: manifest.offerId is not a known assessment offer
768
+ code: invalid_body
769
+ "401":
770
+ $ref: "#/components/responses/Unauthorized"
771
+ "403":
772
+ description: Caller cannot validate this workspace.
773
+ content:
774
+ application/json:
775
+ schema:
776
+ $ref: "#/components/schemas/ErrorMessageDto"
777
+ examples:
778
+ not_member:
779
+ value:
780
+ error: Forbidden
781
+ workspace_admin_required:
782
+ value:
783
+ error: Forbidden
784
+ code: workspace_admin_required
785
+ cross_workspace_forbidden:
786
+ value:
787
+ error: Forbidden
788
+ code: cross_workspace_forbidden
789
+ "404":
790
+ description: Workspace does not exist.
791
+ content:
792
+ application/json:
793
+ schema:
794
+ $ref: "#/components/schemas/ErrorMessageDto"
795
+ example:
796
+ error: Not found
797
+
798
+ /workspaces/{workspaceId}/assessment-manifest/build:
799
+ post:
800
+ tags: [Workspace]
801
+ summary: Build and validate this workspace's assessment manifest from stored evidence
802
+ operationId: buildWorkspaceAssessmentManifest
803
+ description: >
804
+ The client sends only the offer, share mode, the render status of each selected
805
+ section, and the matched-period ROI chapter it printed. The server fills scope,
806
+ excluded repositories, windows, sources with freshness, consent,
807
+ `expectedRepositoryIds`, `soldWindowComplete`, `premiumAiAssessable`, and
808
+ tolerances from stored data, then runs the same fail-closed check as
809
+ `validateWorkspaceAssessmentManifest`.
810
+
811
+
812
+ Scope is the non-archived workspace repositories. Above 50 repositories or 500
813
+ active engineers fails (custom quote). For the premium offer, repositories without
814
+ merged pull requests in both matched windows are listed in `excluded` and left out
815
+ of scope. Baseline windows: `pre` is null, `post` is the 90 calendar days ending
816
+ yesterday (America/New_York), and `transitionDays` is 0. Premium windows come from
817
+ the confirmed rollout day. Freshness: source control 24 hours, AI usage 7 days,
818
+ readiness scans 30 days. Aggregate-use consent is reported as stored; no row is
819
+ `unset`.
820
+
821
+
822
+ `premiumAiAssessable` needs fresh Cursor, Claude, or Copilot usage in the post
823
+ window and complete matched-period evidence. A live ROI refusal for incomplete
824
+ data blocks validation (`rollout_unset`, `window_missing`,
825
+ `ship_weight_incomplete`, `active_engineers_unknown`, `confounder_inputs_missing`,
826
+ `normalization_unset`, `output_unit_mismatch`). A refusal that is a legitimate
827
+ result does not (`non_positive_output`, `confounder`, `missing_cost`,
828
+ `non_usd_cost`, `cost_not_attributable`); the report prints "not assessable" with
829
+ no figure. The premium `roi` tolerance is `unset` when the ROI section did not
830
+ render or `roi` is missing, `exceeded` when the printed chapter differs from the
831
+ server result (USD figures within the dictionary money tolerance), and `within`
832
+ otherwise. Named-person sections always fail.
833
+
834
+
835
+ Send `result.contentHash` with the PDF to `storeAssessmentReport`, which rebuilds
836
+ the manifest and stores only when it is still a validated internal buyer file
837
+ with the same hash.
838
+
839
+
840
+ **Authorization:** Bearer JWT required. Caller must be a workspace admin, or a
841
+ platform admin who is a member of this workspace. Members who are not workspace
842
+ admins receive 403. Non-members of an existing workspace receive 403. Unknown
843
+ workspaces receive 404.
844
+ parameters:
845
+ - $ref: "#/components/parameters/WorkspaceId"
846
+ requestBody:
847
+ required: true
848
+ content:
849
+ application/json:
850
+ schema:
851
+ $ref: "#/components/schemas/BuildAssessmentManifestBodyDto"
852
+ responses:
853
+ "200":
854
+ description: Server-built manifest body and its validation result.
855
+ content:
856
+ application/json:
857
+ schema:
858
+ $ref: "#/components/schemas/BuildAssessmentManifestResponseDto"
859
+ "400":
860
+ description: Body failed validation.
861
+ content:
862
+ application/json:
863
+ schema:
864
+ $ref: "#/components/schemas/ErrorMessageDto"
865
+ example:
866
+ error: offerId is not a known assessment offer
867
+ code: invalid_body
868
+ "401":
869
+ $ref: "#/components/responses/Unauthorized"
870
+ "403":
871
+ description: Caller cannot build this workspace's manifest.
872
+ content:
873
+ application/json:
874
+ schema:
875
+ $ref: "#/components/schemas/ErrorMessageDto"
876
+ example:
877
+ error: Forbidden
878
+ code: workspace_admin_required
879
+ "404":
880
+ description: Workspace does not exist.
881
+ content:
882
+ application/json:
883
+ schema:
884
+ $ref: "#/components/schemas/ErrorMessageDto"
885
+ example:
886
+ error: Not found
887
+ "503":
888
+ description: Stored evidence could not be loaded (no database or a load failure).
889
+ content:
890
+ application/json:
891
+ schema:
892
+ $ref: "#/components/schemas/ErrorMessageDto"
893
+ example:
894
+ error: Service unavailable
895
+ code: evidence_unavailable
896
+
864
897
  /admin/readiness/token-usage:
865
898
  get:
866
899
  tags: [PlatformAdmin]
@@ -7154,26 +7187,25 @@ paths:
7154
7187
  schema:
7155
7188
  $ref: "#/components/schemas/ErrorMessageDto"
7156
7189
 
7157
- /workspaces/{workspaceId}/aggregate-use-consent:
7190
+ /workspaces/{workspaceId}/assessment-rollout:
7158
7191
  get:
7159
7192
  tags: [Workspace]
7160
- summary: Read anonymized aggregate-use permission
7161
- operationId: getAggregateUseConsent
7193
+ summary: Read the customer-confirmed assessment rollout day
7194
+ operationId: getWorkspaceAssessmentRollout
7162
7195
  description: >
7163
- Returns whether ThinkAI may use this workspace's anonymized aggregate results in
7164
- marketing and peer benchmarks. A workspace that never chose returns `unset`, which
7165
- behaves as opted out (`effective: false`). The permission never covers company name,
7166
- logo, quote, raw report, repository or person identity, or customer reference.
7167
- Any workspace member may read it.
7196
+ Returns the AI rollout day the customer confirmed for the Agentic AI ROI
7197
+ assessment, interpreted at 00:00 America/New_York, plus who set it and when.
7198
+ This is the only rollout source for the matched pre and post windows.
7199
+ `rolloutDay` is null until an admin sets it. Any workspace member may read it.
7168
7200
  parameters:
7169
7201
  - $ref: "#/components/parameters/WorkspaceId"
7170
7202
  responses:
7171
7203
  "200":
7172
- description: Current permission and the sentence reports must print
7204
+ description: Current rollout day
7173
7205
  content:
7174
7206
  application/json:
7175
7207
  schema:
7176
- $ref: "#/components/schemas/AggregateUseConsentDto"
7208
+ $ref: "#/components/schemas/AssessmentRolloutDto"
7177
7209
  "401":
7178
7210
  $ref: "#/components/responses/Unauthorized"
7179
7211
  "403":
@@ -7184,17 +7216,21 @@ paths:
7184
7216
  application/json:
7185
7217
  schema:
7186
7218
  $ref: "#/components/schemas/ErrorMessageDto"
7219
+ "503":
7220
+ description: Rollout storage is not configured
7221
+ content:
7222
+ application/json:
7223
+ schema:
7224
+ $ref: "#/components/schemas/ErrorMessageDto"
7187
7225
  put:
7188
7226
  tags: [Workspace]
7189
- summary: Set anonymized aggregate-use permission
7190
- operationId: putAggregateUseConsent
7227
+ summary: Set the customer-confirmed assessment rollout day
7228
+ operationId: putWorkspaceAssessmentRollout
7191
7229
  description: >
7192
- Workspace admins opt in or out. Every change writes one
7193
- `workspace.aggregate_use.changed` activity-log row in the same transaction.
7194
- Moving from `opt_in` to `opt_out` records a withdrawal and stops future extraction.
7195
- Opt-in requires the current legal-approved notice version; while no notice is
7196
- approved, opt-in returns `409 aggregate_use_notice_unapproved`. Opt-out is always
7197
- accepted. A refusal does not change price, delivery, support, or assessment output.
7230
+ Workspace admins, or platform admins who are workspace members, set the rollout
7231
+ day. The day must be a real calendar date that has already started in
7232
+ America/New_York. Each save records the caller and time and writes one
7233
+ `workspace.assessment_rollout.changed` activity-log row.
7198
7234
  parameters:
7199
7235
  - $ref: "#/components/parameters/WorkspaceId"
7200
7236
  requestBody:
@@ -7202,16 +7238,16 @@ paths:
7202
7238
  content:
7203
7239
  application/json:
7204
7240
  schema:
7205
- $ref: "#/components/schemas/AggregateUseConsentPutDto"
7241
+ $ref: "#/components/schemas/AssessmentRolloutPutDto"
7206
7242
  responses:
7207
7243
  "200":
7208
- description: Saved permission
7244
+ description: Saved rollout day
7209
7245
  content:
7210
7246
  application/json:
7211
7247
  schema:
7212
- $ref: "#/components/schemas/AggregateUseConsentDto"
7248
+ $ref: "#/components/schemas/AssessmentRolloutDto"
7213
7249
  "400":
7214
- description: "Invalid body (`code`: `invalid_input`)"
7250
+ description: "Invalid body, impossible date, or future day (`code`: `invalid_input`)"
7215
7251
  content:
7216
7252
  application/json:
7217
7253
  schema:
@@ -7230,45 +7266,38 @@ paths:
7230
7266
  application/json:
7231
7267
  schema:
7232
7268
  $ref: "#/components/schemas/ErrorMessageDto"
7233
- "409":
7234
- description: >
7235
- Opt-in refused because no notice is approved (`aggregate_use_notice_unapproved`)
7236
- or the submitted notice version is not current (`aggregate_use_notice_stale`).
7269
+ "503":
7270
+ description: Rollout storage is not configured
7237
7271
  content:
7238
7272
  application/json:
7239
7273
  schema:
7240
7274
  $ref: "#/components/schemas/ErrorMessageDto"
7241
7275
 
7242
- /workspaces/{workspaceId}/report-center/exports:
7243
- post:
7244
- tags: [WorkspaceActivity]
7245
- summary: Record a Report Center PDF export
7246
- operationId: recordReportCenterExport
7276
+ /workspaces/{workspaceId}/assessment-roi:
7277
+ get:
7278
+ tags: [Workspace]
7279
+ summary: Read the matched-period ROI from stored evidence
7280
+ operationId: getWorkspaceAssessmentRoi
7247
7281
  description: >
7248
- Workspace admin (or platform admin) records that a Report Center PDF was downloaded.
7249
- Inserts one `report.exported` activity-log row with the request body as secret-free metadata.
7250
- Does not store PDF bytes. Members who are not workspace admins receive 403.
7282
+ Derives the 90-day pre window, the 14-day transition, and the 90-day post
7283
+ window from the confirmed rollout day, then loads calendar-day coverage,
7284
+ merged pull request Ship Weight per window, and recorded USD AI spend for the
7285
+ post window. The figure is USD cost per incremental normalized Ship Weight
7286
+ (per active engineer and Mon–Fri America/New_York working day). Fails closed:
7287
+ `status` is `not_assessable` with a `reason` when a window is incomplete, cost
7288
+ is missing, non-USD, or not attributable by day, Ship Weight is unscored,
7289
+ active engineers are not countable, incremental output is not positive, or a
7290
+ confounder is flagged or cannot be checked. Cost is never estimated. Any
7291
+ workspace member may read it.
7251
7292
  parameters:
7252
7293
  - $ref: "#/components/parameters/WorkspaceId"
7253
- requestBody:
7254
- required: true
7255
- content:
7256
- application/json:
7257
- schema:
7258
- $ref: "#/components/schemas/ReportCenterExportRecordBodyDto"
7259
7294
  responses:
7260
- "201":
7261
- description: Export recorded
7262
- content:
7263
- application/json:
7264
- schema:
7265
- $ref: "#/components/schemas/ReportCenterExportRecordResponseDto"
7266
- "400":
7267
- description: Invalid body
7295
+ "200":
7296
+ description: Matched-period ROI result
7268
7297
  content:
7269
7298
  application/json:
7270
7299
  schema:
7271
- $ref: "#/components/schemas/ErrorMessageDto"
7300
+ $ref: "#/components/schemas/AssessmentRoiDto"
7272
7301
  "401":
7273
7302
  $ref: "#/components/responses/Unauthorized"
7274
7303
  "403":
@@ -7279,37 +7308,521 @@ paths:
7279
7308
  application/json:
7280
7309
  schema:
7281
7310
  $ref: "#/components/schemas/ErrorMessageDto"
7282
- "501":
7283
- description: Activity log is not supported by this backend build
7311
+ "503":
7312
+ description: Evidence storage is not configured or could not be read
7284
7313
  content:
7285
7314
  application/json:
7286
7315
  schema:
7287
7316
  $ref: "#/components/schemas/ErrorMessageDto"
7288
7317
 
7289
- /workspaces/{workspaceId}/activity:
7318
+ /workspaces/{workspaceId}/aggregate-use-consent:
7290
7319
  get:
7291
- tags: [WorkspaceActivity]
7292
- summary: Workspace activity log (paginated)
7293
- operationId: listWorkspaceActivity
7320
+ tags: [Workspace]
7321
+ summary: Read anonymized aggregate-use permission
7322
+ operationId: getAggregateUseConsent
7294
7323
  description: >
7295
- Paginated, chronological feed of workspace-scoped lifecycle events: repository add/remove/re-add
7296
- and GitHub integration add/remove. Inactive repos remain visible in this log.
7297
- Filter by a single `eventType`, a coarse `eventCategory`/`productArea`, an `eventSubcategory`
7298
- (a dotted event-type prefix that matches a whole lifecycle subgroup in one call, e.g.
7299
- `integrations.cursor.execution_key`), `actorUserId`, or date range (`since`/`until`).
7324
+ Returns whether ThinkAI may use this workspace's anonymized aggregate results in
7325
+ marketing and peer benchmarks. A workspace that never chose returns `unset`, which
7326
+ behaves as opted out (`effective: false`). The permission never covers company name,
7327
+ logo, quote, raw report, repository or person identity, or customer reference.
7328
+ Any workspace member may read it.
7300
7329
  parameters:
7301
7330
  - $ref: "#/components/parameters/WorkspaceId"
7302
- - $ref: "#/components/parameters/PaginationLimit"
7303
- - $ref: "#/components/parameters/PaginationOffset"
7304
- - $ref: "#/components/parameters/PaginationOrder"
7305
- - name: sort
7306
- in: query
7307
- required: false
7308
- description: >
7309
- Server-side sort column (the activity "5 W's"). Combined with `order` (asc/desc).
7310
- Defaults to `when` (occurredAt). `occurredAt`/`id` is always the tiebreaker.
7311
- schema:
7312
- type: string
7331
+ responses:
7332
+ "200":
7333
+ description: Current permission and the sentence reports must print
7334
+ content:
7335
+ application/json:
7336
+ schema:
7337
+ $ref: "#/components/schemas/AggregateUseConsentDto"
7338
+ "401":
7339
+ $ref: "#/components/responses/Unauthorized"
7340
+ "403":
7341
+ $ref: "#/components/responses/Forbidden"
7342
+ "404":
7343
+ description: Workspace does not exist
7344
+ content:
7345
+ application/json:
7346
+ schema:
7347
+ $ref: "#/components/schemas/ErrorMessageDto"
7348
+ put:
7349
+ tags: [Workspace]
7350
+ summary: Set anonymized aggregate-use permission
7351
+ operationId: putAggregateUseConsent
7352
+ description: >
7353
+ Workspace admins opt in or out. Every change writes one
7354
+ `workspace.aggregate_use.changed` activity-log row in the same transaction.
7355
+ Moving from `opt_in` to `opt_out` records a withdrawal and stops future extraction.
7356
+ Opt-in requires the current legal-approved notice version; while no notice is
7357
+ approved, opt-in returns `409 aggregate_use_notice_unapproved`. Opt-out is always
7358
+ accepted. A refusal does not change price, delivery, support, or assessment output.
7359
+ parameters:
7360
+ - $ref: "#/components/parameters/WorkspaceId"
7361
+ requestBody:
7362
+ required: true
7363
+ content:
7364
+ application/json:
7365
+ schema:
7366
+ $ref: "#/components/schemas/AggregateUseConsentPutDto"
7367
+ responses:
7368
+ "200":
7369
+ description: Saved permission
7370
+ content:
7371
+ application/json:
7372
+ schema:
7373
+ $ref: "#/components/schemas/AggregateUseConsentDto"
7374
+ "400":
7375
+ description: "Invalid body (`code`: `invalid_input`)"
7376
+ content:
7377
+ application/json:
7378
+ schema:
7379
+ $ref: "#/components/schemas/ErrorMessageDto"
7380
+ "401":
7381
+ $ref: "#/components/responses/Unauthorized"
7382
+ "403":
7383
+ description: "Caller is not a workspace admin (`code`: `workspace_admin_required`)"
7384
+ content:
7385
+ application/json:
7386
+ schema:
7387
+ $ref: "#/components/schemas/ErrorMessageDto"
7388
+ "404":
7389
+ description: Workspace does not exist
7390
+ content:
7391
+ application/json:
7392
+ schema:
7393
+ $ref: "#/components/schemas/ErrorMessageDto"
7394
+ "409":
7395
+ description: >
7396
+ Opt-in refused because no notice is approved (`aggregate_use_notice_unapproved`)
7397
+ or the submitted notice version is not current (`aggregate_use_notice_stale`).
7398
+ content:
7399
+ application/json:
7400
+ schema:
7401
+ $ref: "#/components/schemas/ErrorMessageDto"
7402
+
7403
+ /workspaces/{workspaceId}/report-center/exports:
7404
+ post:
7405
+ tags: [WorkspaceActivity]
7406
+ summary: Record a Report Center PDF export
7407
+ operationId: recordReportCenterExport
7408
+ description: >
7409
+ Workspace admin (or platform admin) records that a Report Center PDF was downloaded.
7410
+ Inserts one `report.exported` activity-log row with the request body as secret-free metadata.
7411
+ Does not store PDF bytes. Members who are not workspace admins receive 403.
7412
+ parameters:
7413
+ - $ref: "#/components/parameters/WorkspaceId"
7414
+ requestBody:
7415
+ required: true
7416
+ content:
7417
+ application/json:
7418
+ schema:
7419
+ $ref: "#/components/schemas/ReportCenterExportRecordBodyDto"
7420
+ responses:
7421
+ "201":
7422
+ description: Export recorded
7423
+ content:
7424
+ application/json:
7425
+ schema:
7426
+ $ref: "#/components/schemas/ReportCenterExportRecordResponseDto"
7427
+ "400":
7428
+ description: Invalid body
7429
+ content:
7430
+ application/json:
7431
+ schema:
7432
+ $ref: "#/components/schemas/ErrorMessageDto"
7433
+ "401":
7434
+ $ref: "#/components/responses/Unauthorized"
7435
+ "403":
7436
+ $ref: "#/components/responses/Forbidden"
7437
+ "404":
7438
+ description: Workspace does not exist
7439
+ content:
7440
+ application/json:
7441
+ schema:
7442
+ $ref: "#/components/schemas/ErrorMessageDto"
7443
+ "501":
7444
+ description: Activity log is not supported by this backend build
7445
+ content:
7446
+ application/json:
7447
+ schema:
7448
+ $ref: "#/components/schemas/ErrorMessageDto"
7449
+
7450
+ /workspaces/{workspaceId}/assessment-reports:
7451
+ get:
7452
+ tags: [AssessmentReports]
7453
+ summary: List stored assessment reports and their secure links
7454
+ operationId: listAssessmentReports
7455
+ description: >
7456
+ Workspace admin (or platform admin who is a workspace member). Lists stored buyer PDFs
7457
+ (metadata only, never bytes) with every secure link grant. Link tokens and token hashes are
7458
+ never returned. A hidden report stays listed: `hidden_restorable` until `restoreUntil`,
7459
+ then `restore_window_ended`. The restore window is restore capability, not erasure.
7460
+ Responses carry `Cache-Control: no-store`.
7461
+ parameters:
7462
+ - $ref: "#/components/parameters/WorkspaceId"
7463
+ responses:
7464
+ "200":
7465
+ description: Stored reports, newest first
7466
+ content:
7467
+ application/json:
7468
+ schema:
7469
+ $ref: "#/components/schemas/AssessmentReportListDto"
7470
+ "401":
7471
+ $ref: "#/components/responses/Unauthorized"
7472
+ "403":
7473
+ $ref: "#/components/responses/Forbidden"
7474
+ "404":
7475
+ description: Workspace does not exist
7476
+ content:
7477
+ application/json:
7478
+ schema:
7479
+ $ref: "#/components/schemas/ErrorMessageDto"
7480
+ "501":
7481
+ description: Stored reports are not supported by this backend build (no database)
7482
+ content:
7483
+ application/json:
7484
+ schema:
7485
+ $ref: "#/components/schemas/ErrorMessageDto"
7486
+ post:
7487
+ tags: [AssessmentReports]
7488
+ summary: Store a validated buyer PDF for secure delivery
7489
+ operationId: storeAssessmentReport
7490
+ description: >
7491
+ Workspace admin (or platform admin who is a workspace member). The client never supplies
7492
+ evidence: the server rebuilds the manifest from stored data with the same builder as
7493
+ `buildWorkspaceAssessmentManifest`, using only `build` (offer, share mode, section
7494
+ status, printed ROI), and stores the PDF only when `buyerDeliverable` is true (validated
7495
+ and internal) and the rebuilt `contentHash` equals the one sent. An external
7496
+ share-safe file is never stored here. The PDF is encrypted with AES-256-GCM and kept in
7497
+ Postgres with its sha256; it is limited to 10 MiB. Records `report.assessment_stored`
7498
+ without report content.
7499
+ parameters:
7500
+ - $ref: "#/components/parameters/WorkspaceId"
7501
+ requestBody:
7502
+ required: true
7503
+ content:
7504
+ application/json:
7505
+ schema:
7506
+ $ref: "#/components/schemas/StoreAssessmentReportBodyDto"
7507
+ responses:
7508
+ "201":
7509
+ description: Report stored
7510
+ content:
7511
+ application/json:
7512
+ schema:
7513
+ $ref: "#/components/schemas/AssessmentReportDto"
7514
+ "400":
7515
+ description: Body is not valid, or the file is not a PDF or is too large
7516
+ content:
7517
+ application/json:
7518
+ schema:
7519
+ $ref: "#/components/schemas/ErrorMessageDto"
7520
+ "401":
7521
+ $ref: "#/components/responses/Unauthorized"
7522
+ "403":
7523
+ $ref: "#/components/responses/Forbidden"
7524
+ "404":
7525
+ description: Workspace does not exist
7526
+ content:
7527
+ application/json:
7528
+ schema:
7529
+ $ref: "#/components/schemas/ErrorMessageDto"
7530
+ "422":
7531
+ description: >
7532
+ `not_buyer_deliverable` (the rebuilt manifest did not validate as an internal buyer
7533
+ file; includes `reasons`) or `manifest_changed` (stored evidence changed since the PDF
7534
+ was built, so `contentHash` differs; export again).
7535
+ content:
7536
+ application/json:
7537
+ schema:
7538
+ $ref: "#/components/schemas/StoreAssessmentReportRejectedDto"
7539
+ "503":
7540
+ description: Stored evidence could not be loaded to rebuild the manifest.
7541
+ content:
7542
+ application/json:
7543
+ schema:
7544
+ $ref: "#/components/schemas/ErrorMessageDto"
7545
+ "501":
7546
+ description: Stored reports are not supported by this backend build (no database)
7547
+ content:
7548
+ application/json:
7549
+ schema:
7550
+ $ref: "#/components/schemas/ErrorMessageDto"
7551
+
7552
+ /workspaces/{workspaceId}/assessment-reports/{reportId}/links:
7553
+ post:
7554
+ tags: [AssessmentReports]
7555
+ summary: Create a secure link and email it to a workspace member
7556
+ operationId: createAssessmentReportLink
7557
+ description: >
7558
+ Workspace admin (or platform admin who is a workspace member). Creates one link grant for a
7559
+ current workspace member and emails them the link only: generic text and
7560
+ `{siteUrl}/reports/secure?t=<opaque token>`. The email carries no PDF attachment, report
7561
+ name, metrics, repository, or person names. Only the sha256 of the token is stored and the
7562
+ token is never returned. Expiry defaults to 14 days and can be shorter. When SMTP is not
7563
+ configured the grant still stands and `email` says why it was skipped. Hidden reports
7564
+ cannot be shared (`409 report_hidden`).
7565
+ parameters:
7566
+ - $ref: "#/components/parameters/WorkspaceId"
7567
+ - $ref: "#/components/parameters/AssessmentReportId"
7568
+ requestBody:
7569
+ required: true
7570
+ content:
7571
+ application/json:
7572
+ schema:
7573
+ $ref: "#/components/schemas/CreateAssessmentReportLinkBodyDto"
7574
+ responses:
7575
+ "201":
7576
+ description: Link created
7577
+ content:
7578
+ application/json:
7579
+ schema:
7580
+ $ref: "#/components/schemas/CreateAssessmentReportLinkResponseDto"
7581
+ "400":
7582
+ description: Body is not valid
7583
+ content:
7584
+ application/json:
7585
+ schema:
7586
+ $ref: "#/components/schemas/ErrorMessageDto"
7587
+ "401":
7588
+ $ref: "#/components/responses/Unauthorized"
7589
+ "403":
7590
+ $ref: "#/components/responses/Forbidden"
7591
+ "404":
7592
+ description: Workspace or report not found (`report_not_found`)
7593
+ content:
7594
+ application/json:
7595
+ schema:
7596
+ $ref: "#/components/schemas/ErrorMessageDto"
7597
+ "409":
7598
+ description: The report is hidden (`report_hidden`)
7599
+ content:
7600
+ application/json:
7601
+ schema:
7602
+ $ref: "#/components/schemas/ErrorMessageDto"
7603
+ "422":
7604
+ description: The recipient is not a current workspace member (`recipient_not_member`)
7605
+ content:
7606
+ application/json:
7607
+ schema:
7608
+ $ref: "#/components/schemas/ErrorMessageDto"
7609
+ "501":
7610
+ description: Stored reports or member listing are not supported by this backend build
7611
+ content:
7612
+ application/json:
7613
+ schema:
7614
+ $ref: "#/components/schemas/ErrorMessageDto"
7615
+
7616
+ /workspaces/{workspaceId}/assessment-reports/{reportId}/links/{grantId}/revoke:
7617
+ post:
7618
+ tags: [AssessmentReports]
7619
+ summary: Revoke a secure link
7620
+ operationId: revokeAssessmentReportLink
7621
+ description: >
7622
+ Workspace admin (or platform admin who is a workspace member). The link stops working
7623
+ immediately. Idempotent: revoking again keeps the first `revokedAt`. Revoking a link does
7624
+ not affect a file the recipient already downloaded. Records `report.assessment_link_revoked`.
7625
+ parameters:
7626
+ - $ref: "#/components/parameters/WorkspaceId"
7627
+ - $ref: "#/components/parameters/AssessmentReportId"
7628
+ - name: grantId
7629
+ in: path
7630
+ required: true
7631
+ schema:
7632
+ type: string
7633
+ format: uuid
7634
+ responses:
7635
+ "200":
7636
+ description: Revoked link
7637
+ content:
7638
+ application/json:
7639
+ schema:
7640
+ $ref: "#/components/schemas/AssessmentReportLinkDto"
7641
+ "401":
7642
+ $ref: "#/components/responses/Unauthorized"
7643
+ "403":
7644
+ $ref: "#/components/responses/Forbidden"
7645
+ "404":
7646
+ description: Workspace, report, or link not found
7647
+ content:
7648
+ application/json:
7649
+ schema:
7650
+ $ref: "#/components/schemas/ErrorMessageDto"
7651
+ "501":
7652
+ description: Stored reports are not supported by this backend build (no database)
7653
+ content:
7654
+ application/json:
7655
+ schema:
7656
+ $ref: "#/components/schemas/ErrorMessageDto"
7657
+
7658
+ /workspaces/{workspaceId}/assessment-reports/{reportId}/hide:
7659
+ post:
7660
+ tags: [AssessmentReports]
7661
+ summary: Hide a stored report
7662
+ operationId: hideAssessmentReport
7663
+ description: >
7664
+ Workspace admin (or platform admin who is a workspace member). Same semantics as workspace
7665
+ delete: the report is hidden immediately and every link stops downloading. It can be
7666
+ restored for 30 days (`restoreUntil`). That window is restore capability, not erasure; no
7667
+ backup purge date is promised, and a file the buyer already downloaded stays with them.
7668
+ Idempotent. Records `report.assessment_hidden`.
7669
+ parameters:
7670
+ - $ref: "#/components/parameters/WorkspaceId"
7671
+ - $ref: "#/components/parameters/AssessmentReportId"
7672
+ responses:
7673
+ "200":
7674
+ description: Hidden report (`links` is empty in this response; list again for links)
7675
+ content:
7676
+ application/json:
7677
+ schema:
7678
+ $ref: "#/components/schemas/AssessmentReportDto"
7679
+ "401":
7680
+ $ref: "#/components/responses/Unauthorized"
7681
+ "403":
7682
+ $ref: "#/components/responses/Forbidden"
7683
+ "404":
7684
+ description: Workspace or report not found
7685
+ content:
7686
+ application/json:
7687
+ schema:
7688
+ $ref: "#/components/schemas/ErrorMessageDto"
7689
+ "501":
7690
+ description: Stored reports are not supported by this backend build (no database)
7691
+ content:
7692
+ application/json:
7693
+ schema:
7694
+ $ref: "#/components/schemas/ErrorMessageDto"
7695
+
7696
+ /workspaces/{workspaceId}/assessment-reports/{reportId}/restore:
7697
+ post:
7698
+ tags: [AssessmentReports]
7699
+ summary: Restore a hidden report within the 30-day window
7700
+ operationId: restoreAssessmentReport
7701
+ description: >
7702
+ Workspace admin (or platform admin who is a workspace member). Allowed only before
7703
+ `restoreUntil`; afterwards it fails with `409 restore_window_ended`. Links that were not
7704
+ revoked or expired work again after restore. Records `report.assessment_restored`.
7705
+ parameters:
7706
+ - $ref: "#/components/parameters/WorkspaceId"
7707
+ - $ref: "#/components/parameters/AssessmentReportId"
7708
+ responses:
7709
+ "200":
7710
+ description: Restored report (`links` is empty in this response; list again for links)
7711
+ content:
7712
+ application/json:
7713
+ schema:
7714
+ $ref: "#/components/schemas/AssessmentReportDto"
7715
+ "401":
7716
+ $ref: "#/components/responses/Unauthorized"
7717
+ "403":
7718
+ $ref: "#/components/responses/Forbidden"
7719
+ "404":
7720
+ description: Workspace or report not found
7721
+ content:
7722
+ application/json:
7723
+ schema:
7724
+ $ref: "#/components/schemas/ErrorMessageDto"
7725
+ "409":
7726
+ description: "`not_hidden` or `restore_window_ended`"
7727
+ content:
7728
+ application/json:
7729
+ schema:
7730
+ $ref: "#/components/schemas/ErrorMessageDto"
7731
+ "501":
7732
+ description: Stored reports are not supported by this backend build (no database)
7733
+ content:
7734
+ application/json:
7735
+ schema:
7736
+ $ref: "#/components/schemas/ErrorMessageDto"
7737
+
7738
+ /assessment-reports/secure:
7739
+ get:
7740
+ tags: [AssessmentReports]
7741
+ summary: Download a stored report through a secure link
7742
+ operationId: downloadAssessmentReport
7743
+ description: >
7744
+ Bearer JWT required. The caller must be the link recipient and a current member of the
7745
+ report's workspace. Access follows `decideReportAccess`: `leaked_token`, `removed_member`,
7746
+ and `wrong_workspace` answer 403; `expired` and `revoked` answer 410; `invalid_grant`
7747
+ answers 404. A hidden report answers `410 report_hidden`. Every response carries
7748
+ `Cache-Control: no-store`. Records `report.assessment_downloaded` without report content.
7749
+ parameters:
7750
+ - name: t
7751
+ in: query
7752
+ required: true
7753
+ description: Opaque link token from the email (`secureLinkQuery`).
7754
+ schema:
7755
+ type: string
7756
+ responses:
7757
+ "200":
7758
+ description: The decrypted PDF
7759
+ headers:
7760
+ Cache-Control:
7761
+ schema:
7762
+ type: string
7763
+ enum: [no-store]
7764
+ content:
7765
+ application/pdf:
7766
+ schema:
7767
+ type: string
7768
+ format: binary
7769
+ "401":
7770
+ $ref: "#/components/responses/Unauthorized"
7771
+ "403":
7772
+ description: "`leaked_token`, `removed_member`, or `wrong_workspace`"
7773
+ content:
7774
+ application/json:
7775
+ schema:
7776
+ $ref: "#/components/schemas/AssessmentReportAccessErrorDto"
7777
+ "404":
7778
+ description: "`invalid_grant`"
7779
+ content:
7780
+ application/json:
7781
+ schema:
7782
+ $ref: "#/components/schemas/AssessmentReportAccessErrorDto"
7783
+ "410":
7784
+ description: "`expired`, `revoked`, or `report_hidden`"
7785
+ content:
7786
+ application/json:
7787
+ schema:
7788
+ $ref: "#/components/schemas/AssessmentReportAccessErrorDto"
7789
+ "500":
7790
+ description: The stored report could not be decrypted (`report_unreadable`)
7791
+ content:
7792
+ application/json:
7793
+ schema:
7794
+ $ref: "#/components/schemas/ErrorMessageDto"
7795
+ "501":
7796
+ description: Stored reports are not supported by this backend build (no database)
7797
+ content:
7798
+ application/json:
7799
+ schema:
7800
+ $ref: "#/components/schemas/ErrorMessageDto"
7801
+
7802
+ /workspaces/{workspaceId}/activity:
7803
+ get:
7804
+ tags: [WorkspaceActivity]
7805
+ summary: Workspace activity log (paginated)
7806
+ operationId: listWorkspaceActivity
7807
+ description: >
7808
+ Paginated, chronological feed of workspace-scoped lifecycle events: repository add/remove/re-add
7809
+ and GitHub integration add/remove. Inactive repos remain visible in this log.
7810
+ Filter by a single `eventType`, a coarse `eventCategory`/`productArea`, an `eventSubcategory`
7811
+ (a dotted event-type prefix that matches a whole lifecycle subgroup in one call, e.g.
7812
+ `integrations.cursor.execution_key`), `actorUserId`, or date range (`since`/`until`).
7813
+ parameters:
7814
+ - $ref: "#/components/parameters/WorkspaceId"
7815
+ - $ref: "#/components/parameters/PaginationLimit"
7816
+ - $ref: "#/components/parameters/PaginationOffset"
7817
+ - $ref: "#/components/parameters/PaginationOrder"
7818
+ - name: sort
7819
+ in: query
7820
+ required: false
7821
+ description: >
7822
+ Server-side sort column (the activity "5 W's"). Combined with `order` (asc/desc).
7823
+ Defaults to `when` (occurredAt). `occurredAt`/`id` is always the tiebreaker.
7824
+ schema:
7825
+ type: string
7313
7826
  enum: [when, who, what, why, where]
7314
7827
  - name: productArea
7315
7828
  in: query
@@ -10989,7 +11502,7 @@ paths:
10989
11502
  derives teams client-side and saves the org structure via `POST /workspaces/{workspaceId}/org-chart`.
10990
11503
  Required columns: employee_id, name, email, title, department, team, manager_id, location.
10991
11504
  Optional: github_username (GitHub login for productivity ingest), bitbucket_nickname,
10992
- bitbucket_uuid, alternate_emails (comma-separated alternate work emails for AI tool spend
11505
+ bitbucket_uuid, gitlab_username (GitLab.com or self-managed username), alternate_emails (comma-separated alternate work emails for AI tool spend
10993
11506
  attribution). Saving the SPA snapshot also materializes a scoring `org_chart` tree for
10994
11507
  team/person metrics when import members are present. Parse-only responses may include
10995
11508
  duplicateEmailWarnings, identityEmailPreview (cross-check against connected AI tool seat
@@ -12005,6 +12518,106 @@ paths:
12005
12518
  "403":
12006
12519
  $ref: "#/components/responses/Forbidden"
12007
12520
 
12521
+ /workspaces/{workspaceId}/sources/discover:
12522
+ post:
12523
+ tags: [Workspace]
12524
+ summary: List repositories a GitLab or Bitbucket source can read (does not persist)
12525
+ description: >
12526
+ Lists projects (GitLab.com, self-managed GitLab) or repositories (Bitbucket Cloud,
12527
+ Bitbucket Data Center) for repository selection. Nothing is stored. Self-managed and
12528
+ Data Center `baseUrl` must be `https`, public, and without credentials; private,
12529
+ loopback, and link-local hosts are rejected before any request. `env:` token
12530
+ references are rejected for self-hosted providers and never resolved from the request
12531
+ for cloud providers. A blank or `***` token reuses the stored source with the same
12532
+ provider, host, and account, and requires the editor or admin role. The list covers
12533
+ the whole account (group or workspace/project key), not only `projects`/`repos`.
12534
+ `repoCap` is the standard repository cap (50); ingest refuses to auto-select more
12535
+ than that. Tokens are never returned.
12536
+ operationId: postWorkspaceVcsRepoDiscovery
12537
+ parameters:
12538
+ - $ref: "#/components/parameters/WorkspaceId"
12539
+ requestBody:
12540
+ required: true
12541
+ content:
12542
+ application/json:
12543
+ schema:
12544
+ $ref: "#/components/schemas/GitVcsSourceDto"
12545
+ examples:
12546
+ gitlabSelfManaged:
12547
+ summary: Self-managed GitLab group
12548
+ value:
12549
+ type: gitlab
12550
+ deployment: self_managed
12551
+ baseUrl: https://gitlab.example.com
12552
+ group: platform
12553
+ token: glpat-example
12554
+ bitbucketDataCenter:
12555
+ summary: Bitbucket Data Center project
12556
+ value:
12557
+ type: bitbucket
12558
+ deployment: data_center
12559
+ baseUrl: https://bitbucket.example.com
12560
+ workspace: PLAT
12561
+ token: "***"
12562
+ responses:
12563
+ "200":
12564
+ description: Repositories the source can read
12565
+ content:
12566
+ application/json:
12567
+ schema:
12568
+ $ref: "#/components/schemas/VcsRepoDiscoveryResponseDto"
12569
+ "400":
12570
+ description: >
12571
+ Invalid source (`error` is a code such as `base_url_required`, `base_url_invalid`,
12572
+ `base_url_not_https`, `base_url_has_credentials`, `base_url_has_path`,
12573
+ `base_url_host_blocked`, `env_ref_not_allowed`, `account_missing`, `token_missing`,
12574
+ `unsupported_type`).
12575
+ content:
12576
+ application/json:
12577
+ schema:
12578
+ $ref: "#/components/schemas/ErrorMessageDto"
12579
+ "401":
12580
+ $ref: "#/components/responses/Unauthorized"
12581
+ "403":
12582
+ $ref: "#/components/responses/Forbidden"
12583
+ "502":
12584
+ description: >
12585
+ The provider rejected or failed the listing (`error` is `unauthorized`, `forbidden`,
12586
+ `not_found`, `rate_limited`, or `discovery_failed`).
12587
+ content:
12588
+ application/json:
12589
+ schema:
12590
+ $ref: "#/components/schemas/ErrorMessageDto"
12591
+
12592
+ /workspaces/{workspaceId}/vcs-provider-coverage:
12593
+ get:
12594
+ tags: [Workspace]
12595
+ summary: GitLab and Bitbucket Data Coverage freshness (server-computed)
12596
+ operationId: getWorkspaceVcsProviderCoverage
12597
+ description: >
12598
+ One row per configured GitLab or Bitbucket source. Freshness is computed on the server
12599
+ from batch-written sync rows and succeeded readiness runs; no request field can mark a
12600
+ row fresh. Git is fresh when the last complete ingest finished within
12601
+ `gitFreshnessHours` (24) and no error was recorded after it. A readiness scan is fresh
12602
+ within `readinessScanFreshnessDays` (30, America/New_York calendar days); `scansFresh`
12603
+ requires every in-scope repository to have a fresh scan. A source with an invalid base
12604
+ URL or missing account is `misconfigured`. Requires workspace membership.
12605
+ parameters:
12606
+ - $ref: "#/components/parameters/WorkspaceId"
12607
+ responses:
12608
+ "200":
12609
+ description: Server-computed provider coverage
12610
+ content:
12611
+ application/json:
12612
+ schema:
12613
+ $ref: "#/components/schemas/VcsProviderCoverageDto"
12614
+ "401":
12615
+ $ref: "#/components/responses/Unauthorized"
12616
+ "403":
12617
+ $ref: "#/components/responses/Forbidden"
12618
+ "501":
12619
+ description: Store does not support provider coverage
12620
+
12008
12621
  /workspaces/{workspaceId}/ingest/productivity/repos/{slug}/retry:
12009
12622
  post:
12010
12623
  tags: [Workspace]
@@ -12915,6 +13528,14 @@ components:
12915
13528
  required: true
12916
13529
  schema:
12917
13530
  type: string
13531
+ AssessmentReportId:
13532
+ name: reportId
13533
+ in: path
13534
+ required: true
13535
+ schema:
13536
+ type: string
13537
+ format: uuid
13538
+ description: Stored assessment report id (`tai_workspace_assessment_reports.id`).
12918
13539
  EvalSuiteId:
12919
13540
  name: suiteId
12920
13541
  in: path
@@ -13315,29 +13936,196 @@ components:
13315
13936
  enum: [asc, desc]
13316
13937
  default: asc
13317
13938
 
13318
- responses:
13319
- Unauthorized:
13320
- description: Missing or invalid bearer token
13321
- content:
13322
- application/json:
13323
- schema:
13324
- $ref: "#/components/schemas/ErrorMessageDto"
13325
- Forbidden:
13326
- description: Authenticated but not allowed
13327
- content:
13328
- application/json:
13329
- schema:
13330
- $ref: "#/components/schemas/ErrorMessageDto"
13331
- ZenHrSyncInProgress:
13332
- description: HRIS sync or ZenHR OAuth refresh already in progress for this workspace
13333
- content:
13334
- application/json:
13335
- schema:
13336
- $ref: "#/components/schemas/ErrorMessageDto"
13337
- example:
13338
- error: zenhr_sync_in_progress
13939
+ responses:
13940
+ Unauthorized:
13941
+ description: Missing or invalid bearer token
13942
+ content:
13943
+ application/json:
13944
+ schema:
13945
+ $ref: "#/components/schemas/ErrorMessageDto"
13946
+ Forbidden:
13947
+ description: Authenticated but not allowed
13948
+ content:
13949
+ application/json:
13950
+ schema:
13951
+ $ref: "#/components/schemas/ErrorMessageDto"
13952
+ ZenHrSyncInProgress:
13953
+ description: HRIS sync or ZenHR OAuth refresh already in progress for this workspace
13954
+ content:
13955
+ application/json:
13956
+ schema:
13957
+ $ref: "#/components/schemas/ErrorMessageDto"
13958
+ example:
13959
+ error: zenhr_sync_in_progress
13960
+
13961
+ schemas:
13962
+ AssessmentManifestValidationBodyDto:
13963
+ description: Body of runAssessmentManifestValidation; buildWorkspaceAssessmentManifest returns the server-built object as `validation`.
13964
+ type: object
13965
+ required:
13966
+ - manifest
13967
+ - workspaceId
13968
+ - expectedWorkspaceId
13969
+ - expectedRepositoryIds
13970
+ - soldWindowComplete
13971
+ - premiumAiAssessable
13972
+ - namedPersonSectionIds
13973
+ - tolerances
13974
+ properties:
13975
+ manifest:
13976
+ type: object
13977
+ required:
13978
+ - offerId
13979
+ - dictionaryVersion
13980
+ - validation
13981
+ - shareMode
13982
+ - watermark
13983
+ - scope
13984
+ - windows
13985
+ - sources
13986
+ - consent
13987
+ - sections
13988
+ properties:
13989
+ offerId:
13990
+ type: string
13991
+ enum: [engineering_readiness_baseline, agentic_ai_roi]
13992
+ dictionaryVersion:
13993
+ type: string
13994
+ validation:
13995
+ type: string
13996
+ enum: [validated, not_validated]
13997
+ shareMode:
13998
+ type: string
13999
+ enum: [internal, external]
14000
+ watermark:
14001
+ type: string
14002
+ nullable: true
14003
+ enum: ["NOT VALIDATED", "SAMPLE · SYNTHETIC DATA"]
14004
+ scope:
14005
+ type: object
14006
+ required: [repositories, excluded]
14007
+ properties:
14008
+ repositories:
14009
+ type: array
14010
+ items:
14011
+ type: object
14012
+ required: [id, name]
14013
+ properties:
14014
+ id: { type: string }
14015
+ name: { type: string }
14016
+ excluded:
14017
+ type: array
14018
+ items:
14019
+ type: object
14020
+ required: [id, name, reason]
14021
+ properties:
14022
+ id: { type: string }
14023
+ name: { type: string }
14024
+ reason: { type: string }
14025
+ windows:
14026
+ type: object
14027
+ required: [pre, post, transitionDays]
14028
+ properties:
14029
+ pre:
14030
+ type: object
14031
+ nullable: true
14032
+ required: [start, end]
14033
+ properties:
14034
+ start: { type: string }
14035
+ end: { type: string }
14036
+ post:
14037
+ type: object
14038
+ nullable: true
14039
+ required: [start, end]
14040
+ properties:
14041
+ start: { type: string }
14042
+ end: { type: string }
14043
+ transitionDays:
14044
+ type: integer
14045
+ sources:
14046
+ type: array
14047
+ items:
14048
+ type: object
14049
+ required: [provider, status, freshAt]
14050
+ properties:
14051
+ provider: { type: string }
14052
+ status: { type: string }
14053
+ freshAt: { type: string, nullable: true }
14054
+ consent:
14055
+ type: object
14056
+ required: [aggregateUse, recordedAt]
14057
+ properties:
14058
+ aggregateUse:
14059
+ type: string
14060
+ enum: [opt_in, opt_out, unset]
14061
+ recordedAt:
14062
+ type: string
14063
+ nullable: true
14064
+ sections:
14065
+ type: array
14066
+ items:
14067
+ type: object
14068
+ required: [id, status]
14069
+ properties:
14070
+ id: { type: string }
14071
+ status:
14072
+ type: string
14073
+ enum: [ok, failed, omitted]
14074
+ reason: { type: string }
14075
+ workspaceId:
14076
+ type: string
14077
+ expectedWorkspaceId:
14078
+ type: string
14079
+ expectedRepositoryIds:
14080
+ type: array
14081
+ items:
14082
+ type: string
14083
+ soldWindowComplete:
14084
+ type: boolean
14085
+ premiumAiAssessable:
14086
+ type: boolean
14087
+ namedPersonSectionIds:
14088
+ type: array
14089
+ items:
14090
+ type: string
14091
+ tolerances:
14092
+ type: array
14093
+ items:
14094
+ type: object
14095
+ required: [id, status]
14096
+ properties:
14097
+ id:
14098
+ type: string
14099
+ status:
14100
+ type: string
14101
+ enum: [unset, exceeded, within]
14102
+
14103
+ AssessmentManifestValidationResultDto:
14104
+ type: object
14105
+ required: [buyerDeliverable, validation, watermark, reasons, contentHash]
14106
+ properties:
14107
+ buyerDeliverable:
14108
+ type: boolean
14109
+ validation:
14110
+ type: string
14111
+ enum: [validated, not_validated]
14112
+ watermark:
14113
+ type: string
14114
+ nullable: true
14115
+ enum: ["NOT VALIDATED"]
14116
+ reasons:
14117
+ type: array
14118
+ items:
14119
+ type: object
14120
+ required: [code, message]
14121
+ properties:
14122
+ code:
14123
+ type: string
14124
+ message:
14125
+ type: string
14126
+ contentHash:
14127
+ type: string
13339
14128
 
13340
- schemas:
13341
14129
  TeamsInstallUrlDto:
13342
14130
  type: object
13343
14131
  required: [url]
@@ -13678,6 +14466,9 @@ components:
13678
14466
  bitbucketUuid:
13679
14467
  type: string
13680
14468
  description: Bitbucket account UUID (e.g. "{aeefb6b0-...}") to attribute merged PRs and reviews to this person in productivity ingest. Preferred over bitbucketNickname — Bitbucket PR payloads expose a stable uuid but not nickname. Resolves to the same person_id (email) as githubUsername.
14469
+ gitlabUsername:
14470
+ type: string
14471
+ description: GitLab username (no @; GitLab.com or self-managed) to map merged merge requests and commits to this person in productivity ingest and the assessment active-engineer count. Stored lowercased. Resolves to the same person_id (email) as githubUsername.
13681
14472
  alternateEmails:
13682
14473
  type: string
13683
14474
  description: Optional comma-separated work emails that map to the same person for AI tool spend/usage (mirrors multi-login github_username). Primary email remains canonical person_id.
@@ -13752,6 +14543,8 @@ components:
13752
14543
  type: string
13753
14544
  bitbucketUuid:
13754
14545
  type: string
14546
+ gitlabUsername:
14547
+ type: string
13755
14548
  alternateEmails:
13756
14549
  type: string
13757
14550
  description: Comma-separated alternate work emails for AI tool attribution.
@@ -19072,17 +19865,199 @@ components:
19072
19865
  - platform-governance
19073
19866
  - delivery
19074
19867
 
19868
+ ExternalVcsProviderDto:
19869
+ type: string
19870
+ enum: [gitlab_cloud, gitlab_self_managed, bitbucket_cloud, bitbucket_server]
19871
+ description: >
19872
+ `bitbucket_server` is Bitbucket Data Center. Self-hosted providers
19873
+ (`gitlab_self_managed`, `bitbucket_server`) always carry `hostBaseUrl`.
19874
+
19875
+ GitVcsSourceDto:
19876
+ type: object
19877
+ description: >
19878
+ GitLab or Bitbucket tenant source entry. GitLab: optional `group` (full path) and
19879
+ `projects` (`group/sub/project`). Bitbucket: `workspace` (Cloud workspace or Data Center
19880
+ project key) and optional `repos` (repository slugs). `deployment` selects self-hosted;
19881
+ it then requires `baseUrl`.
19882
+ required: [type]
19883
+ additionalProperties: true
19884
+ properties:
19885
+ type:
19886
+ type: string
19887
+ enum: [gitlab, bitbucket]
19888
+ deployment:
19889
+ type: string
19890
+ enum: [cloud, self_managed, data_center]
19891
+ baseUrl:
19892
+ type: string
19893
+ maxLength: 512
19894
+ group:
19895
+ type: string
19896
+ maxLength: 512
19897
+ projects:
19898
+ type: array
19899
+ items:
19900
+ type: string
19901
+ maxLength: 512
19902
+ workspace:
19903
+ type: string
19904
+ maxLength: 256
19905
+ repos:
19906
+ type: array
19907
+ items:
19908
+ type: string
19909
+ maxLength: 256
19910
+ token:
19911
+ type: string
19912
+ maxLength: 4096
19913
+ description: Personal / project / HTTP access token, or `***` to reuse the stored token.
19914
+
19915
+ VcsDiscoveredRepoDto:
19916
+ type: object
19917
+ required: [slug, name, defaultBranch, webUrl]
19918
+ properties:
19919
+ slug:
19920
+ type: string
19921
+ description: "`group/sub/project` (GitLab) or `workspace/repo` / `PROJECT/repo` (Bitbucket)"
19922
+ name:
19923
+ type: string
19924
+ defaultBranch:
19925
+ type: string
19926
+ nullable: true
19927
+ webUrl:
19928
+ type: string
19929
+ nullable: true
19930
+
19931
+ VcsRepoDiscoveryResponseDto:
19932
+ type: object
19933
+ required: [provider, hostBaseUrl, account, truncated, repoCap, repositories]
19934
+ properties:
19935
+ provider:
19936
+ $ref: "#/components/schemas/ExternalVcsProviderDto"
19937
+ hostBaseUrl:
19938
+ type: string
19939
+ nullable: true
19940
+ account:
19941
+ type: string
19942
+ description: GitLab group path (empty = token membership), Bitbucket workspace, or project key
19943
+ truncated:
19944
+ type: boolean
19945
+ description: True when the provider page limit was reached; the list is incomplete.
19946
+ repoCap:
19947
+ type: integer
19948
+ repositories:
19949
+ type: array
19950
+ items:
19951
+ $ref: "#/components/schemas/VcsDiscoveredRepoDto"
19952
+
19953
+ VcsProviderCoverageRowDto:
19954
+ type: object
19955
+ required:
19956
+ - provider
19957
+ - hostBaseUrl
19958
+ - account
19959
+ - selfHosted
19960
+ - state
19961
+ - isFresh
19962
+ - lastIngestSuccessAt
19963
+ - lastErrorKind
19964
+ - lastDiscoveryAt
19965
+ - discoveredRepoCount
19966
+ - staleForMs
19967
+ - repoCount
19968
+ - scannedRepoCount
19969
+ - freshScanRepoCount
19970
+ - scansFresh
19971
+ - mergedHistorySince
19972
+ - commitHistorySince
19973
+ properties:
19974
+ provider:
19975
+ $ref: "#/components/schemas/ExternalVcsProviderDto"
19976
+ hostBaseUrl:
19977
+ type: string
19978
+ nullable: true
19979
+ account:
19980
+ type: string
19981
+ selfHosted:
19982
+ type: boolean
19983
+ state:
19984
+ type: string
19985
+ enum: [fresh, stale, never_synced, error, misconfigured]
19986
+ isFresh:
19987
+ type: boolean
19988
+ description: Server-computed; true only when `state` is `fresh`.
19989
+ lastIngestSuccessAt:
19990
+ type: string
19991
+ format: date-time
19992
+ nullable: true
19993
+ lastErrorKind:
19994
+ type: string
19995
+ nullable: true
19996
+ lastDiscoveryAt:
19997
+ type: string
19998
+ format: date-time
19999
+ nullable: true
20000
+ discoveredRepoCount:
20001
+ type: integer
20002
+ nullable: true
20003
+ staleForMs:
20004
+ type: integer
20005
+ nullable: true
20006
+ description: Milliseconds past the Git freshness limit when `state` is `stale`.
20007
+ repoCount:
20008
+ type: integer
20009
+ description: In-scope repositories stored for this source.
20010
+ scannedRepoCount:
20011
+ type: integer
20012
+ freshScanRepoCount:
20013
+ type: integer
20014
+ scansFresh:
20015
+ type: boolean
20016
+ mergedHistorySince:
20017
+ type: string
20018
+ format: date-time
20019
+ nullable: true
20020
+ description: >-
20021
+ Earliest merge time reached by the assessment merged-request history backfill for this
20022
+ source; null when no backfill window has been written.
20023
+ commitHistorySince:
20024
+ type: string
20025
+ format: date-time
20026
+ nullable: true
20027
+ description: >-
20028
+ Instant from which every in-scope repository of this source has contiguous
20029
+ default-branch commit-author coverage (latest per-repo coverage start); null when any
20030
+ repository has no coverage.
20031
+
20032
+ VcsProviderCoverageDto:
20033
+ type: object
20034
+ required: [generatedAt, gitFreshnessHours, readinessScanFreshnessDays, sources]
20035
+ properties:
20036
+ generatedAt:
20037
+ type: string
20038
+ format: date-time
20039
+ gitFreshnessHours:
20040
+ type: integer
20041
+ readinessScanFreshnessDays:
20042
+ type: integer
20043
+ sources:
20044
+ type: array
20045
+ items:
20046
+ $ref: "#/components/schemas/VcsProviderCoverageRowDto"
20047
+
19075
20048
  RepoProviderDto:
19076
20049
  type: object
19077
20050
  required: [kind, slug, webUrl]
19078
20051
  properties:
19079
20052
  kind:
19080
20053
  type: string
19081
- enum: [github, bitbucket_cloud, bitbucket_server]
20054
+ enum: [github, bitbucket_cloud, bitbucket_server, gitlab_cloud, gitlab_self_managed]
19082
20055
  slug:
19083
20056
  type: string
19084
20057
  maxLength: 256
19085
- description: owner/repo (GitHub), workspace/repo_slug (Bitbucket Cloud), or PROJECT_KEY/repo_slug (Bitbucket Server)
20058
+ description: >
20059
+ owner/repo (GitHub), workspace/repo_slug (Bitbucket Cloud), PROJECT_KEY/repo_slug
20060
+ (Bitbucket Server / Data Center), or group/sub/project (GitLab)
19086
20061
  webUrl:
19087
20062
  type: string
19088
20063
  format: uri
@@ -19100,7 +20075,9 @@ components:
19100
20075
  type: string
19101
20076
  format: uri
19102
20077
  nullable: true
19103
- description: Required for bitbucket_server (self-hosted); null for cloud providers
20078
+ description: >
20079
+ Required for bitbucket_server and gitlab_self_managed (self-hosted); null for cloud
20080
+ providers
19104
20081
 
19105
20082
  ReadinessAnalyzeBodyDto:
19106
20083
  type: object
@@ -22277,29 +23254,309 @@ components:
22277
23254
  - integrations.zelt.added
22278
23255
  - integrations.zelt.removed
22279
23256
  - report.exported
23257
+ - report.assessment_stored
23258
+ - report.assessment_link_sent
23259
+ - report.assessment_link_revoked
23260
+ - report.assessment_downloaded
23261
+ - report.assessment_hidden
23262
+ - report.assessment_restored
22280
23263
  - workspace.aggregate_use.changed
23264
+ - workspace.assessment_rollout.changed
23265
+
23266
+ AggregateUseNoticeStatusDto:
23267
+ type: object
23268
+ additionalProperties: false
23269
+ required: [status]
23270
+ description: Current legal-approved opt-in notice, or `unset` while opt-in is unavailable.
23271
+ properties:
23272
+ status:
23273
+ type: string
23274
+ enum: [approved, unset]
23275
+ version:
23276
+ type: string
23277
+ description: Present only when `status` is `approved`. Send it back on opt-in.
23278
+
23279
+ AssessmentNamedEventDto:
23280
+ type: string
23281
+ enum: [major_release, migration, acquisition, policy_change]
23282
+ description: Confounder-relevant named event from the assessment dictionary.
23283
+
23284
+ AssessmentRolloutDto:
23285
+ type: object
23286
+ additionalProperties: false
23287
+ required:
23288
+ - rolloutDay
23289
+ - timezone
23290
+ - boundary
23291
+ - startsAt
23292
+ - setBy
23293
+ - setAt
23294
+ - namedEvents
23295
+ - namedEventsConfirmedBy
23296
+ - namedEventsConfirmedAt
23297
+ properties:
23298
+ rolloutDay:
23299
+ type: string
23300
+ format: date
23301
+ nullable: true
23302
+ description: Confirmed rollout day (`YYYY-MM-DD`); null until set.
23303
+ timezone:
23304
+ type: string
23305
+ enum: [America/New_York]
23306
+ boundary:
23307
+ type: string
23308
+ enum: [midnight]
23309
+ startsAt:
23310
+ type: string
23311
+ format: date-time
23312
+ nullable: true
23313
+ description: UTC instant of 00:00 America/New_York on `rolloutDay`.
23314
+ setBy:
23315
+ type: object
23316
+ nullable: true
23317
+ additionalProperties: false
23318
+ required: [userId, displayName]
23319
+ properties:
23320
+ userId:
23321
+ type: string
23322
+ format: uuid
23323
+ displayName:
23324
+ type: string
23325
+ nullable: true
23326
+ setAt:
23327
+ type: string
23328
+ format: date-time
23329
+ nullable: true
23330
+ namedEvents:
23331
+ type: array
23332
+ nullable: true
23333
+ items:
23334
+ $ref: "#/components/schemas/AssessmentNamedEventDto"
23335
+ description: >
23336
+ Named events confirmed during either matched window. Null means
23337
+ the customer or operator has not confirmed yet, so the ROI
23338
+ confounder check reports `confounder_inputs_missing`. An empty
23339
+ array confirms none occurred.
23340
+ namedEventsConfirmedBy:
23341
+ type: object
23342
+ nullable: true
23343
+ additionalProperties: false
23344
+ required: [userId, displayName]
23345
+ properties:
23346
+ userId:
23347
+ type: string
23348
+ format: uuid
23349
+ displayName:
23350
+ type: string
23351
+ nullable: true
23352
+ namedEventsConfirmedAt:
23353
+ type: string
23354
+ format: date-time
23355
+ nullable: true
23356
+
23357
+ AssessmentRolloutPutDto:
23358
+ type: object
23359
+ additionalProperties: false
23360
+ required: [rolloutDay]
23361
+ properties:
23362
+ rolloutDay:
23363
+ type: string
23364
+ format: date
23365
+ description: Real calendar date, not after today in America/New_York.
23366
+ namedEvents:
23367
+ type: array
23368
+ items:
23369
+ $ref: "#/components/schemas/AssessmentNamedEventDto"
23370
+ description: >
23371
+ Omit to leave the stored named-event confirmation unchanged. Send
23372
+ an empty array to confirm no named event occurred during either
23373
+ matched window, or the events that did.
23374
+
23375
+ AssessmentRoiReasonDto:
23376
+ type: string
23377
+ enum:
23378
+ - rollout_unset
23379
+ - window_missing
23380
+ - missing_cost
23381
+ - non_usd_cost
23382
+ - cost_not_attributable
23383
+ - ship_weight_incomplete
23384
+ - active_engineers_unknown
23385
+ - non_positive_output
23386
+ - normalization_unset
23387
+ - confounder
23388
+ - confounder_inputs_missing
23389
+ - output_unit_mismatch
23390
+ description: >
23391
+ Why the matched-period ROI is not assessable. `cost_not_attributable` means a
23392
+ billing-cycle bill (GitHub Copilot) overlaps the post window and cannot be split
23393
+ by day. `active_engineers_unknown` means stored identities cannot yet be counted.
23394
+ `confounder_inputs_missing` means a named-event, headcount, or repository-scope
23395
+ check has no stored input.
23396
+
23397
+ AssessmentRoiDayRangeDto:
23398
+ type: object
23399
+ additionalProperties: false
23400
+ required: [from, to]
23401
+ properties:
23402
+ from:
23403
+ type: string
23404
+ format: date
23405
+ to:
23406
+ type: string
23407
+ format: date
23408
+
23409
+ AssessmentRoiWindowDto:
23410
+ type: object
23411
+ additionalProperties: false
23412
+ required:
23413
+ - from
23414
+ - to
23415
+ - requiredDays
23416
+ - coveredDays
23417
+ - complete
23418
+ - workingDays
23419
+ - activeEngineers
23420
+ - mergedPullRequests
23421
+ - shipWeight
23422
+ - normalizedShipWeight
23423
+ properties:
23424
+ from:
23425
+ type: string
23426
+ format: date
23427
+ to:
23428
+ type: string
23429
+ format: date
23430
+ requiredDays:
23431
+ type: integer
23432
+ description: Calendar days in the window (90).
23433
+ coveredDays:
23434
+ type: integer
23435
+ description: Calendar days with stored evidence.
23436
+ complete:
23437
+ type: boolean
23438
+ description: Every calendar day is covered and the window ended before today.
23439
+ workingDays:
23440
+ type: integer
23441
+ description: Monday–Friday days in America/New_York. No holidays.
23442
+ activeEngineers:
23443
+ type: integer
23444
+ nullable: true
23445
+ mergedPullRequests:
23446
+ type: integer
23447
+ shipWeight:
23448
+ type: number
23449
+ nullable: true
23450
+ description: Sum of stored Ship Weight for unique merged pull requests; null if any is unscored.
23451
+ normalizedShipWeight:
23452
+ type: number
23453
+ nullable: true
23454
+ description: Ship Weight per active engineer per working day.
22281
23455
 
22282
- UnsetAssessmentDecisionDto:
23456
+ AssessmentRoiDto:
22283
23457
  type: object
22284
23458
  additionalProperties: false
22285
- required: [status]
23459
+ required:
23460
+ - dictionaryVersion
23461
+ - rolloutDay
23462
+ - rolloutBoundary
23463
+ - windows
23464
+ - cost
23465
+ - confounders
23466
+ - status
23467
+ - reason
23468
+ - incrementalNormalizedShipWeight
23469
+ - costPerIncrementalShipWeightUsd
23470
+ - costPerMergedPullRequestUsd
22286
23471
  properties:
22287
- status:
23472
+ dictionaryVersion:
22288
23473
  type: string
22289
- enum: [unset]
22290
-
22291
- AggregateUseNoticeStatusDto:
22292
- type: object
22293
- additionalProperties: false
22294
- required: [status]
22295
- description: Current legal-approved opt-in notice, or `unset` while opt-in is unavailable.
22296
- properties:
22297
- status:
23474
+ rolloutDay:
22298
23475
  type: string
22299
- enum: [approved, unset]
22300
- version:
23476
+ format: date
23477
+ nullable: true
23478
+ rolloutBoundary:
23479
+ type: object
23480
+ additionalProperties: false
23481
+ required: [timezone, boundary, startsAt]
23482
+ properties:
23483
+ timezone:
23484
+ type: string
23485
+ enum: [America/New_York]
23486
+ boundary:
23487
+ type: string
23488
+ enum: [midnight]
23489
+ startsAt:
23490
+ type: string
23491
+ format: date-time
23492
+ nullable: true
23493
+ windows:
23494
+ type: object
23495
+ additionalProperties: false
23496
+ required: [pre, transition, post]
23497
+ properties:
23498
+ pre:
23499
+ allOf:
23500
+ - $ref: "#/components/schemas/AssessmentRoiWindowDto"
23501
+ nullable: true
23502
+ transition:
23503
+ allOf:
23504
+ - $ref: "#/components/schemas/AssessmentRoiDayRangeDto"
23505
+ nullable: true
23506
+ post:
23507
+ allOf:
23508
+ - $ref: "#/components/schemas/AssessmentRoiWindowDto"
23509
+ nullable: true
23510
+ cost:
23511
+ type: object
23512
+ additionalProperties: false
23513
+ required: [amountUsd, currency, providers]
23514
+ description: Recorded daily AI spend inside the post window. Never estimated.
23515
+ properties:
23516
+ amountUsd:
23517
+ type: number
23518
+ nullable: true
23519
+ currency:
23520
+ type: string
23521
+ enum: [USD]
23522
+ nullable: true
23523
+ providers:
23524
+ type: array
23525
+ items:
23526
+ type: string
23527
+ confounders:
23528
+ type: object
23529
+ additionalProperties: false
23530
+ required: [namedEvent, headcountChangeRatio, repositoryScopeChangeRatio]
23531
+ properties:
23532
+ namedEvent:
23533
+ type: boolean
23534
+ nullable: true
23535
+ description: Null when no named-event input is stored.
23536
+ headcountChangeRatio:
23537
+ type: number
23538
+ nullable: true
23539
+ repositoryScopeChangeRatio:
23540
+ type: number
23541
+ nullable: true
23542
+ description: Repositories with merged pull requests in only one window, over pre-window repositories.
23543
+ status:
22301
23544
  type: string
22302
- description: Present only when `status` is `approved`. Send it back on opt-in.
23545
+ enum: [assessable, not_assessable]
23546
+ reason:
23547
+ allOf:
23548
+ - $ref: "#/components/schemas/AssessmentRoiReasonDto"
23549
+ nullable: true
23550
+ incrementalNormalizedShipWeight:
23551
+ type: number
23552
+ nullable: true
23553
+ costPerIncrementalShipWeightUsd:
23554
+ type: number
23555
+ nullable: true
23556
+ costPerMergedPullRequestUsd:
23557
+ type: number
23558
+ nullable: true
23559
+ description: Separate correlation. Never the sold ROI figure.
22303
23560
 
22304
23561
  AggregateUseConsentDto:
22305
23562
  type: object
@@ -22404,7 +23661,7 @@ components:
22404
23661
  properties:
22405
23662
  version:
22406
23663
  type: string
22407
- enum: ["2026-10-20.1"]
23664
+ enum: ["2026-10-24.1"]
22408
23665
  periodDays:
22409
23666
  type: integer
22410
23667
  enum: [90]
@@ -22576,9 +23833,21 @@ components:
22576
23833
  - countryAllowList
22577
23834
  properties:
22578
23835
  baselinePrice:
22579
- $ref: "#/components/schemas/UnsetAssessmentDecisionDto"
23836
+ type: object
23837
+ additionalProperties: false
23838
+ required: [status, amountUsd, taxes]
23839
+ properties:
23840
+ status: { type: string, enum: [set] }
23841
+ amountUsd: { type: integer, enum: [4999] }
23842
+ taxes: { type: string, enum: [extra] }
22580
23843
  premiumPrice:
22581
- $ref: "#/components/schemas/UnsetAssessmentDecisionDto"
23844
+ type: object
23845
+ additionalProperties: false
23846
+ required: [status, amountUsd, taxes]
23847
+ properties:
23848
+ status: { type: string, enum: [set] }
23849
+ amountUsd: { type: integer, enum: [9999] }
23850
+ taxes: { type: string, enum: [extra] }
22582
23851
  currency:
22583
23852
  type: object
22584
23853
  additionalProperties: false
@@ -22641,7 +23910,18 @@ components:
22641
23910
  enum: [monday, tuesday, wednesday, thursday, friday]
22642
23911
  holidays: { type: string, enum: [none] }
22643
23912
  countryAllowList:
22644
- $ref: "#/components/schemas/UnsetAssessmentDecisionDto"
23913
+ type: object
23914
+ additionalProperties: false
23915
+ required: [status, countries, counselAccepted]
23916
+ description: Owner decision without counsel acceptance; `counselAccepted` stays false.
23917
+ properties:
23918
+ status: { type: string, enum: [set] }
23919
+ countries:
23920
+ type: array
23921
+ items:
23922
+ type: string
23923
+ enum: [US, BH, KW, OM, QA, SA, AE]
23924
+ counselAccepted: { type: boolean, enum: [false] }
22645
23925
 
22646
23926
  ReportCenterExportRecordBodyDto:
22647
23927
  type: object
@@ -22727,6 +24007,286 @@ components:
22727
24007
  minLength: 1
22728
24008
  maxLength: 256
22729
24009
 
24010
+ StoreAssessmentReportBodyDto:
24011
+ type: object
24012
+ additionalProperties: false
24013
+ required: [pdfBase64, build, contentHash]
24014
+ properties:
24015
+ pdfBase64:
24016
+ type: string
24017
+ description: The buyer PDF, base64-encoded. Must start with `%PDF-`; at most 10 MiB decoded.
24018
+ build:
24019
+ $ref: "#/components/schemas/BuildAssessmentManifestBodyDto"
24020
+ contentHash:
24021
+ type: string
24022
+ pattern: "^[0-9a-f]{64}$"
24023
+ description: >
24024
+ `result.contentHash` from the `buildWorkspaceAssessmentManifest` call the PDF was
24025
+ rendered from. The server rebuilds the manifest and rejects a different hash.
24026
+
24027
+ BuildAssessmentManifestBodyDto:
24028
+ type: object
24029
+ additionalProperties: false
24030
+ required: [offerId, shareMode, sections]
24031
+ properties:
24032
+ offerId:
24033
+ type: string
24034
+ enum: [engineering_readiness_baseline, agentic_ai_roi]
24035
+ shareMode:
24036
+ type: string
24037
+ enum: [internal, external]
24038
+ sections:
24039
+ type: array
24040
+ minItems: 1
24041
+ maxItems: 64
24042
+ description: Every selected section with its render status. Ids are unique.
24043
+ items:
24044
+ type: object
24045
+ additionalProperties: false
24046
+ required: [id, status]
24047
+ properties:
24048
+ id:
24049
+ type: string
24050
+ minLength: 1
24051
+ maxLength: 128
24052
+ status:
24053
+ type: string
24054
+ enum: [ok, failed, omitted]
24055
+ reason:
24056
+ type: string
24057
+ maxLength: 500
24058
+ roi:
24059
+ description: >
24060
+ The matched-period ROI chapter as printed, copied from the
24061
+ `getWorkspaceAssessmentRoi` result it rendered. Null or omitted when the chapter
24062
+ did not print one.
24063
+ allOf:
24064
+ - $ref: "#/components/schemas/PrintedAssessmentRoiDto"
24065
+ nullable: true
24066
+
24067
+ PrintedAssessmentRoiDto:
24068
+ type: object
24069
+ additionalProperties: false
24070
+ required:
24071
+ - status
24072
+ - reason
24073
+ - incrementalNormalizedShipWeight
24074
+ - costPerIncrementalShipWeightUsd
24075
+ - costPerMergedPullRequestUsd
24076
+ properties:
24077
+ status:
24078
+ type: string
24079
+ enum: [assessable, not_assessable]
24080
+ reason:
24081
+ type: string
24082
+ nullable: true
24083
+ incrementalNormalizedShipWeight:
24084
+ type: number
24085
+ nullable: true
24086
+ costPerIncrementalShipWeightUsd:
24087
+ type: number
24088
+ nullable: true
24089
+ costPerMergedPullRequestUsd:
24090
+ type: number
24091
+ nullable: true
24092
+
24093
+ BuildAssessmentManifestResponseDto:
24094
+ type: object
24095
+ required: [validation, result]
24096
+ properties:
24097
+ validation:
24098
+ $ref: "#/components/schemas/AssessmentManifestValidationBodyDto"
24099
+ result:
24100
+ $ref: "#/components/schemas/AssessmentManifestValidationResultDto"
24101
+
24102
+ StoreAssessmentReportRejectedDto:
24103
+ type: object
24104
+ required: [error, code]
24105
+ properties:
24106
+ error:
24107
+ type: string
24108
+ code:
24109
+ type: string
24110
+ enum: [not_buyer_deliverable, manifest_changed]
24111
+ validation:
24112
+ type: string
24113
+ enum: [validated, not_validated]
24114
+ reasons:
24115
+ type: array
24116
+ items:
24117
+ type: object
24118
+ required: [code, message]
24119
+ properties:
24120
+ code:
24121
+ type: string
24122
+ message:
24123
+ type: string
24124
+
24125
+ AssessmentReportLinkDto:
24126
+ type: object
24127
+ required:
24128
+ [
24129
+ id,
24130
+ reportId,
24131
+ recipientUserId,
24132
+ recipientEmail,
24133
+ recipientName,
24134
+ expiresAt,
24135
+ revokedAt,
24136
+ createdAt,
24137
+ lastAccessedAt,
24138
+ status,
24139
+ ]
24140
+ properties:
24141
+ id:
24142
+ type: string
24143
+ format: uuid
24144
+ reportId:
24145
+ type: string
24146
+ format: uuid
24147
+ recipientUserId:
24148
+ type: string
24149
+ format: uuid
24150
+ recipientEmail:
24151
+ type: string
24152
+ nullable: true
24153
+ description: Null when the recipient is no longer a workspace member.
24154
+ recipientName:
24155
+ type: string
24156
+ nullable: true
24157
+ expiresAt:
24158
+ type: string
24159
+ format: date-time
24160
+ revokedAt:
24161
+ type: string
24162
+ format: date-time
24163
+ nullable: true
24164
+ createdAt:
24165
+ type: string
24166
+ format: date-time
24167
+ lastAccessedAt:
24168
+ type: string
24169
+ format: date-time
24170
+ nullable: true
24171
+ status:
24172
+ type: string
24173
+ enum: [active, expired, revoked]
24174
+
24175
+ AssessmentReportDto:
24176
+ type: object
24177
+ required:
24178
+ [
24179
+ id,
24180
+ offerId,
24181
+ sizeBytes,
24182
+ sha256,
24183
+ manifestHash,
24184
+ createdAt,
24185
+ createdBy,
24186
+ hiddenAt,
24187
+ restoreUntil,
24188
+ status,
24189
+ links,
24190
+ ]
24191
+ properties:
24192
+ id:
24193
+ type: string
24194
+ format: uuid
24195
+ offerId:
24196
+ type: string
24197
+ enum: [engineering_readiness_baseline, agentic_ai_roi]
24198
+ sizeBytes:
24199
+ type: integer
24200
+ sha256:
24201
+ type: string
24202
+ description: sha256 of the plaintext PDF.
24203
+ manifestHash:
24204
+ type: string
24205
+ description: "`contentHash` of the validated manifest."
24206
+ createdAt:
24207
+ type: string
24208
+ format: date-time
24209
+ createdBy:
24210
+ type: string
24211
+ format: uuid
24212
+ nullable: true
24213
+ hiddenAt:
24214
+ type: string
24215
+ format: date-time
24216
+ nullable: true
24217
+ restoreUntil:
24218
+ type: string
24219
+ format: date-time
24220
+ nullable: true
24221
+ description: End of the restore window. Not an erasure or backup purge date.
24222
+ status:
24223
+ type: string
24224
+ enum: [available, hidden_restorable, restore_window_ended]
24225
+ links:
24226
+ type: array
24227
+ items:
24228
+ $ref: "#/components/schemas/AssessmentReportLinkDto"
24229
+
24230
+ AssessmentReportListDto:
24231
+ type: object
24232
+ required: [restoreWindowDays, secureLinkDays, reports]
24233
+ properties:
24234
+ restoreWindowDays:
24235
+ type: integer
24236
+ enum: [30]
24237
+ secureLinkDays:
24238
+ type: integer
24239
+ enum: [14]
24240
+ description: Default and maximum secure link lifetime in days.
24241
+ reports:
24242
+ type: array
24243
+ items:
24244
+ $ref: "#/components/schemas/AssessmentReportDto"
24245
+
24246
+ CreateAssessmentReportLinkBodyDto:
24247
+ type: object
24248
+ additionalProperties: false
24249
+ required: [recipientUserId]
24250
+ properties:
24251
+ recipientUserId:
24252
+ type: string
24253
+ format: uuid
24254
+ description: A current workspace member (`TeamMemberDto.id`).
24255
+ expiresInDays:
24256
+ type: integer
24257
+ minimum: 1
24258
+ maximum: 14
24259
+ default: 14
24260
+
24261
+ CreateAssessmentReportLinkResponseDto:
24262
+ type: object
24263
+ required: [link, email]
24264
+ properties:
24265
+ link:
24266
+ $ref: "#/components/schemas/AssessmentReportLinkDto"
24267
+ email:
24268
+ type: string
24269
+ enum: [sent, skipped_missing_smtp, skipped_missing_base_url, failed]
24270
+
24271
+ AssessmentReportAccessErrorDto:
24272
+ type: object
24273
+ required: [error, code]
24274
+ properties:
24275
+ error:
24276
+ type: string
24277
+ code:
24278
+ type: string
24279
+ enum:
24280
+ [
24281
+ leaked_token,
24282
+ removed_member,
24283
+ wrong_workspace,
24284
+ expired,
24285
+ revoked,
24286
+ invalid_grant,
24287
+ report_hidden,
24288
+ ]
24289
+
22730
24290
  ReportCenterExportRecordResponseDto:
22731
24291
  type: object
22732
24292
  required: [ok, activityEntryId]