@company-semantics/contracts 83.1.0 → 84.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +299 -11
- package/src/generated/openapi-routes.ts +4 -0
- package/src/source-sync/README.md +59 -1
- package/src/source-sync/__tests__/approval-schemas.test.ts +2 -2
- package/src/source-sync/__tests__/barrel.test.ts +14 -0
- package/src/source-sync/__tests__/candidate-totals.test.ts +119 -0
- package/src/source-sync/__tests__/schemas.test.ts +236 -15
- package/src/source-sync/__tests__/vocabulary.test.ts +135 -1
- package/src/source-sync/candidate-totals.ts +20 -2
- package/src/source-sync/index.ts +40 -1
- package/src/source-sync/relevance.ts +216 -0
- package/src/source-sync/schemas.ts +106 -59
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = '53f462b4c90b' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '53f462b4c90ba419e71d55d82e19bb6a9836d95aeaaae6299b8124ac340320d8' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -3367,7 +3367,7 @@ export interface paths {
|
|
|
3367
3367
|
};
|
|
3368
3368
|
/**
|
|
3369
3369
|
* What I owe and what happened, merged, newest first
|
|
3370
|
-
* @description Actionable rows first, then informational rows
|
|
3370
|
+
* @description Actionable rows first (the same in every view), then the informational rows of the requested view (view: all | unread) — unread-and-unresolved first, each by latest activity. Grouped, not chronologically interleaved. Only the informational half paginates.
|
|
3371
3371
|
*/
|
|
3372
3372
|
get: operations["listMyFeed"];
|
|
3373
3373
|
put?: never;
|
|
@@ -3387,7 +3387,10 @@ export interface paths {
|
|
|
3387
3387
|
};
|
|
3388
3388
|
get?: never;
|
|
3389
3389
|
put?: never;
|
|
3390
|
-
/**
|
|
3390
|
+
/**
|
|
3391
|
+
* Mark selected notifications read
|
|
3392
|
+
* @description Selected by ids or by view snapshot (asOf). Only the actor’s own rows.
|
|
3393
|
+
*/
|
|
3391
3394
|
post: operations["markMyFeedRead"];
|
|
3392
3395
|
delete?: never;
|
|
3393
3396
|
options?: never;
|
|
@@ -3412,6 +3415,83 @@ export interface paths {
|
|
|
3412
3415
|
patch?: never;
|
|
3413
3416
|
trace?: never;
|
|
3414
3417
|
};
|
|
3418
|
+
"/api/me/feed/unread": {
|
|
3419
|
+
parameters: {
|
|
3420
|
+
query?: never;
|
|
3421
|
+
header?: never;
|
|
3422
|
+
path?: never;
|
|
3423
|
+
cookie?: never;
|
|
3424
|
+
};
|
|
3425
|
+
get?: never;
|
|
3426
|
+
put?: never;
|
|
3427
|
+
/**
|
|
3428
|
+
* Mark selected notifications unread
|
|
3429
|
+
* @description Selected by ids or by view snapshot (asOf). Resolved and deleted rows are never marked unread.
|
|
3430
|
+
*/
|
|
3431
|
+
post: operations["markMyFeedUnread"];
|
|
3432
|
+
delete?: never;
|
|
3433
|
+
options?: never;
|
|
3434
|
+
head?: never;
|
|
3435
|
+
patch?: never;
|
|
3436
|
+
trace?: never;
|
|
3437
|
+
};
|
|
3438
|
+
"/api/me/feed/delete": {
|
|
3439
|
+
parameters: {
|
|
3440
|
+
query?: never;
|
|
3441
|
+
header?: never;
|
|
3442
|
+
path?: never;
|
|
3443
|
+
cookie?: never;
|
|
3444
|
+
};
|
|
3445
|
+
get?: never;
|
|
3446
|
+
put?: never;
|
|
3447
|
+
/**
|
|
3448
|
+
* Delete selected notifications, undoable by restore
|
|
3449
|
+
* @description Soft-deletes the actor’s own selected rows under one opaque restoreKey. The entity a row is about is never touched.
|
|
3450
|
+
*/
|
|
3451
|
+
post: operations["deleteMyFeedItems"];
|
|
3452
|
+
delete?: never;
|
|
3453
|
+
options?: never;
|
|
3454
|
+
head?: never;
|
|
3455
|
+
patch?: never;
|
|
3456
|
+
trace?: never;
|
|
3457
|
+
};
|
|
3458
|
+
"/api/me/feed/restore": {
|
|
3459
|
+
parameters: {
|
|
3460
|
+
query?: never;
|
|
3461
|
+
header?: never;
|
|
3462
|
+
path?: never;
|
|
3463
|
+
cookie?: never;
|
|
3464
|
+
};
|
|
3465
|
+
get?: never;
|
|
3466
|
+
put?: never;
|
|
3467
|
+
/**
|
|
3468
|
+
* Undo a delete by its restoreKey
|
|
3469
|
+
* @description Restores the rows one delete removed. A row whose condition already has a newer active row stays deleted and is counted as superseded.
|
|
3470
|
+
*/
|
|
3471
|
+
post: operations["restoreMyFeedItems"];
|
|
3472
|
+
delete?: never;
|
|
3473
|
+
options?: never;
|
|
3474
|
+
head?: never;
|
|
3475
|
+
patch?: never;
|
|
3476
|
+
trace?: never;
|
|
3477
|
+
};
|
|
3478
|
+
"/api/me/feed/clear-resolved": {
|
|
3479
|
+
parameters: {
|
|
3480
|
+
query?: never;
|
|
3481
|
+
header?: never;
|
|
3482
|
+
path?: never;
|
|
3483
|
+
cookie?: never;
|
|
3484
|
+
};
|
|
3485
|
+
get?: never;
|
|
3486
|
+
put?: never;
|
|
3487
|
+
/** Delete every resolved notification, undoable by restore */
|
|
3488
|
+
post: operations["clearMyFeedResolved"];
|
|
3489
|
+
delete?: never;
|
|
3490
|
+
options?: never;
|
|
3491
|
+
head?: never;
|
|
3492
|
+
patch?: never;
|
|
3493
|
+
trace?: never;
|
|
3494
|
+
};
|
|
3415
3495
|
"/api/me/meetings/recordings/start": {
|
|
3416
3496
|
parameters: {
|
|
3417
3497
|
query?: never;
|
|
@@ -7715,6 +7795,62 @@ export interface components {
|
|
|
7715
7795
|
unreadCount: number;
|
|
7716
7796
|
truncated: boolean;
|
|
7717
7797
|
};
|
|
7798
|
+
FeedMutationResponse: {
|
|
7799
|
+
affected: number;
|
|
7800
|
+
};
|
|
7801
|
+
FeedMarkReadRequest: {
|
|
7802
|
+
selector: {
|
|
7803
|
+
/** @constant */
|
|
7804
|
+
kind: "ids";
|
|
7805
|
+
notificationIds: string[];
|
|
7806
|
+
} | {
|
|
7807
|
+
/** @constant */
|
|
7808
|
+
kind: "view";
|
|
7809
|
+
/** @enum {string} */
|
|
7810
|
+
view: "all" | "unread";
|
|
7811
|
+
/** Format: date-time */
|
|
7812
|
+
asOf: string;
|
|
7813
|
+
};
|
|
7814
|
+
};
|
|
7815
|
+
FeedMarkUnreadRequest: {
|
|
7816
|
+
selector: {
|
|
7817
|
+
/** @constant */
|
|
7818
|
+
kind: "ids";
|
|
7819
|
+
notificationIds: string[];
|
|
7820
|
+
} | {
|
|
7821
|
+
/** @constant */
|
|
7822
|
+
kind: "view";
|
|
7823
|
+
/** @enum {string} */
|
|
7824
|
+
view: "all" | "unread";
|
|
7825
|
+
/** Format: date-time */
|
|
7826
|
+
asOf: string;
|
|
7827
|
+
};
|
|
7828
|
+
};
|
|
7829
|
+
FeedDeleteResponse: {
|
|
7830
|
+
affected: number;
|
|
7831
|
+
restoreKey: string;
|
|
7832
|
+
};
|
|
7833
|
+
FeedDeleteRequest: {
|
|
7834
|
+
selector: {
|
|
7835
|
+
/** @constant */
|
|
7836
|
+
kind: "ids";
|
|
7837
|
+
notificationIds: string[];
|
|
7838
|
+
} | {
|
|
7839
|
+
/** @constant */
|
|
7840
|
+
kind: "view";
|
|
7841
|
+
/** @enum {string} */
|
|
7842
|
+
view: "all" | "unread";
|
|
7843
|
+
/** Format: date-time */
|
|
7844
|
+
asOf: string;
|
|
7845
|
+
};
|
|
7846
|
+
};
|
|
7847
|
+
FeedRestoreResponse: {
|
|
7848
|
+
restored: number;
|
|
7849
|
+
superseded: number;
|
|
7850
|
+
};
|
|
7851
|
+
FeedRestoreRequest: {
|
|
7852
|
+
restoreKey: string;
|
|
7853
|
+
};
|
|
7718
7854
|
/** @description Recording + work_item ids and the transcription session grant. */
|
|
7719
7855
|
StartMeetingRecordingResponse: {
|
|
7720
7856
|
/** @description ULID; unified trace key for the recording (INV-MTG-7). */
|
|
@@ -8740,7 +8876,10 @@ export interface components {
|
|
|
8740
8876
|
targetDocId: string;
|
|
8741
8877
|
targetDocTitle: string;
|
|
8742
8878
|
/** @enum {string} */
|
|
8743
|
-
|
|
8879
|
+
visibility: "hidden" | "shown";
|
|
8880
|
+
/** @enum {string} */
|
|
8881
|
+
classificationState: "unclassified" | "classified";
|
|
8882
|
+
sourceClass: ("board_deck" | "board_minutes" | "investor_update" | "financial_brief" | "budget_forecast" | "okr_plan" | "annual_plan" | "all_hands" | "unit_strategy" | "roadmap" | "qbr" | "other" | "decoy") | null;
|
|
8744
8883
|
periodLabel: string | null;
|
|
8745
8884
|
seriesRole: ("current" | "history") | null;
|
|
8746
8885
|
relevance: number;
|
|
@@ -8750,9 +8889,42 @@ export interface components {
|
|
|
8750
8889
|
sharingBreadth: "restricted_few" | "group" | "domain" | "anyone" | "unknown";
|
|
8751
8890
|
sharedWithCount: number | null;
|
|
8752
8891
|
audienceFit: ("fits" | "widens") | null;
|
|
8753
|
-
|
|
8754
|
-
recommendedUse: "attached" | "direction_only" | "excluded";
|
|
8892
|
+
recommendedUse: ("attached" | "direction_only" | "excluded") | null;
|
|
8755
8893
|
use: ("attached" | "direction_only" | "excluded") | null;
|
|
8894
|
+
selectionSource: ("person" | "approval_default") | null;
|
|
8895
|
+
personHoldReasons: ("relevance_override" | "treatment_selection" | "target_override" | "manual_add")[];
|
|
8896
|
+
/** @enum {string} */
|
|
8897
|
+
machineState: "fresh" | "refreshed" | "filtered_out" | "not_rediscovered" | "pending_classification" | "retired";
|
|
8898
|
+
relevanceReasons: ({
|
|
8899
|
+
/** @constant */
|
|
8900
|
+
kind: "lexicon";
|
|
8901
|
+
/** @enum {string} */
|
|
8902
|
+
code: "title_term" | "folder_term" | "negative_title_term" | "negative_folder_term";
|
|
8903
|
+
term: string;
|
|
8904
|
+
count: number;
|
|
8905
|
+
} | {
|
|
8906
|
+
/** @constant */
|
|
8907
|
+
kind: "signal";
|
|
8908
|
+
/** @enum {string} */
|
|
8909
|
+
code: "folder_probe_hit" | "title_probe_hit" | "version_churn" | "mime_class" | "size_band" | "dated_series" | "recent_edit" | "owner_unit_head" | "owner_executive" | "sharing_breadth" | "duplicate_title" | "personal_mime";
|
|
8910
|
+
value: number | null;
|
|
8911
|
+
} | {
|
|
8912
|
+
/** @constant */
|
|
8913
|
+
kind: "evaluation";
|
|
8914
|
+
/** @enum {string} */
|
|
8915
|
+
code: "not_evaluated_t1" | "not_evaluated_t2" | "folder_unresolved" | "folder_truncated" | "explored_t1" | "explored_t2";
|
|
8916
|
+
} | {
|
|
8917
|
+
/** @constant */
|
|
8918
|
+
kind: "model";
|
|
8919
|
+
/** @enum {string} */
|
|
8920
|
+
code: "title_path_score" | "peek_confirms" | "peek_contradicts";
|
|
8921
|
+
score: number | null;
|
|
8922
|
+
})[];
|
|
8923
|
+
targetAudience: ("restricted" | "unit" | "org_wide") | null;
|
|
8924
|
+
attention: {
|
|
8925
|
+
t1: ("exploit" | "explore") | null;
|
|
8926
|
+
t2: ("exploit" | "explore") | null;
|
|
8927
|
+
};
|
|
8756
8928
|
included: boolean;
|
|
8757
8929
|
treatmentUnresolved: boolean;
|
|
8758
8930
|
matchReason: string;
|
|
@@ -8795,6 +8967,9 @@ export interface components {
|
|
|
8795
8967
|
files: number;
|
|
8796
8968
|
filesIncluded: number;
|
|
8797
8969
|
filesNeedingChoice: number;
|
|
8970
|
+
filesHidden: number;
|
|
8971
|
+
filesNotRediscovered: number;
|
|
8972
|
+
filesPendingClassification: number;
|
|
8798
8973
|
};
|
|
8799
8974
|
scopes: {
|
|
8800
8975
|
scopeKey: string;
|
|
@@ -8817,6 +8992,16 @@ export interface components {
|
|
|
8817
8992
|
highMicroUsd: string;
|
|
8818
8993
|
capMicroUsd: string;
|
|
8819
8994
|
} | null;
|
|
8995
|
+
discovery: {
|
|
8996
|
+
foundThisRun: number;
|
|
8997
|
+
retainedDecisions: number;
|
|
8998
|
+
notRediscovered: number;
|
|
8999
|
+
pendingClassification: number;
|
|
9000
|
+
filesDropped: number;
|
|
9001
|
+
coveragePercent: number;
|
|
9002
|
+
/** @enum {string} */
|
|
9003
|
+
scanOutcome: "complete" | "budget_exhausted" | "file_ceiling" | "stopped";
|
|
9004
|
+
} | null;
|
|
8820
9005
|
};
|
|
8821
9006
|
SourceSyncApproveResponse: {
|
|
8822
9007
|
/** Format: uuid */
|
|
@@ -15352,7 +15537,8 @@ export interface operations {
|
|
|
15352
15537
|
};
|
|
15353
15538
|
listMyFeed: {
|
|
15354
15539
|
parameters: {
|
|
15355
|
-
query
|
|
15540
|
+
query: {
|
|
15541
|
+
view: "all" | "unread";
|
|
15356
15542
|
cursor?: string;
|
|
15357
15543
|
};
|
|
15358
15544
|
header?: never;
|
|
@@ -15379,14 +15565,20 @@ export interface operations {
|
|
|
15379
15565
|
path?: never;
|
|
15380
15566
|
cookie?: never;
|
|
15381
15567
|
};
|
|
15382
|
-
requestBody
|
|
15568
|
+
requestBody: {
|
|
15569
|
+
content: {
|
|
15570
|
+
"application/json": components["schemas"]["FeedMarkReadRequest"];
|
|
15571
|
+
};
|
|
15572
|
+
};
|
|
15383
15573
|
responses: {
|
|
15384
|
-
/** @description
|
|
15574
|
+
/** @description How many of the actor’s own rows were marked read */
|
|
15385
15575
|
200: {
|
|
15386
15576
|
headers: {
|
|
15387
15577
|
[name: string]: unknown;
|
|
15388
15578
|
};
|
|
15389
|
-
content
|
|
15579
|
+
content: {
|
|
15580
|
+
"application/json": components["schemas"]["FeedMutationResponse"];
|
|
15581
|
+
};
|
|
15390
15582
|
};
|
|
15391
15583
|
};
|
|
15392
15584
|
};
|
|
@@ -15399,12 +15591,106 @@ export interface operations {
|
|
|
15399
15591
|
};
|
|
15400
15592
|
requestBody?: never;
|
|
15401
15593
|
responses: {
|
|
15402
|
-
/** @description
|
|
15594
|
+
/** @description How many of the actor’s own rows were marked read */
|
|
15403
15595
|
200: {
|
|
15404
15596
|
headers: {
|
|
15405
15597
|
[name: string]: unknown;
|
|
15406
15598
|
};
|
|
15407
|
-
content
|
|
15599
|
+
content: {
|
|
15600
|
+
"application/json": components["schemas"]["FeedMutationResponse"];
|
|
15601
|
+
};
|
|
15602
|
+
};
|
|
15603
|
+
};
|
|
15604
|
+
};
|
|
15605
|
+
markMyFeedUnread: {
|
|
15606
|
+
parameters: {
|
|
15607
|
+
query?: never;
|
|
15608
|
+
header?: never;
|
|
15609
|
+
path?: never;
|
|
15610
|
+
cookie?: never;
|
|
15611
|
+
};
|
|
15612
|
+
requestBody: {
|
|
15613
|
+
content: {
|
|
15614
|
+
"application/json": components["schemas"]["FeedMarkUnreadRequest"];
|
|
15615
|
+
};
|
|
15616
|
+
};
|
|
15617
|
+
responses: {
|
|
15618
|
+
/** @description How many of the actor’s own rows were marked unread */
|
|
15619
|
+
200: {
|
|
15620
|
+
headers: {
|
|
15621
|
+
[name: string]: unknown;
|
|
15622
|
+
};
|
|
15623
|
+
content: {
|
|
15624
|
+
"application/json": components["schemas"]["FeedMutationResponse"];
|
|
15625
|
+
};
|
|
15626
|
+
};
|
|
15627
|
+
};
|
|
15628
|
+
};
|
|
15629
|
+
deleteMyFeedItems: {
|
|
15630
|
+
parameters: {
|
|
15631
|
+
query?: never;
|
|
15632
|
+
header?: never;
|
|
15633
|
+
path?: never;
|
|
15634
|
+
cookie?: never;
|
|
15635
|
+
};
|
|
15636
|
+
requestBody: {
|
|
15637
|
+
content: {
|
|
15638
|
+
"application/json": components["schemas"]["FeedDeleteRequest"];
|
|
15639
|
+
};
|
|
15640
|
+
};
|
|
15641
|
+
responses: {
|
|
15642
|
+
/** @description How many rows were deleted, and the key that restores exactly them */
|
|
15643
|
+
200: {
|
|
15644
|
+
headers: {
|
|
15645
|
+
[name: string]: unknown;
|
|
15646
|
+
};
|
|
15647
|
+
content: {
|
|
15648
|
+
"application/json": components["schemas"]["FeedDeleteResponse"];
|
|
15649
|
+
};
|
|
15650
|
+
};
|
|
15651
|
+
};
|
|
15652
|
+
};
|
|
15653
|
+
restoreMyFeedItems: {
|
|
15654
|
+
parameters: {
|
|
15655
|
+
query?: never;
|
|
15656
|
+
header?: never;
|
|
15657
|
+
path?: never;
|
|
15658
|
+
cookie?: never;
|
|
15659
|
+
};
|
|
15660
|
+
requestBody: {
|
|
15661
|
+
content: {
|
|
15662
|
+
"application/json": components["schemas"]["FeedRestoreRequest"];
|
|
15663
|
+
};
|
|
15664
|
+
};
|
|
15665
|
+
responses: {
|
|
15666
|
+
/** @description How many rows were restored, and how many a newer row superseded */
|
|
15667
|
+
200: {
|
|
15668
|
+
headers: {
|
|
15669
|
+
[name: string]: unknown;
|
|
15670
|
+
};
|
|
15671
|
+
content: {
|
|
15672
|
+
"application/json": components["schemas"]["FeedRestoreResponse"];
|
|
15673
|
+
};
|
|
15674
|
+
};
|
|
15675
|
+
};
|
|
15676
|
+
};
|
|
15677
|
+
clearMyFeedResolved: {
|
|
15678
|
+
parameters: {
|
|
15679
|
+
query?: never;
|
|
15680
|
+
header?: never;
|
|
15681
|
+
path?: never;
|
|
15682
|
+
cookie?: never;
|
|
15683
|
+
};
|
|
15684
|
+
requestBody?: never;
|
|
15685
|
+
responses: {
|
|
15686
|
+
/** @description How many resolved rows were deleted, and the key that restores them */
|
|
15687
|
+
200: {
|
|
15688
|
+
headers: {
|
|
15689
|
+
[name: string]: unknown;
|
|
15690
|
+
};
|
|
15691
|
+
content: {
|
|
15692
|
+
"application/json": components["schemas"]["FeedDeleteResponse"];
|
|
15693
|
+
};
|
|
15408
15694
|
};
|
|
15409
15695
|
};
|
|
15410
15696
|
};
|
|
@@ -17647,6 +17933,8 @@ export interface operations {
|
|
|
17647
17933
|
requestBody: {
|
|
17648
17934
|
content: {
|
|
17649
17935
|
"application/json": {
|
|
17936
|
+
/** @constant */
|
|
17937
|
+
visibility?: "shown";
|
|
17650
17938
|
/** @enum {string} */
|
|
17651
17939
|
use?: "attached" | "direction_only" | "excluded";
|
|
17652
17940
|
/** @enum {string} */
|
|
@@ -103,8 +103,12 @@ export const openApiRoutes = {
|
|
|
103
103
|
'/api/me/action-items': ['GET'],
|
|
104
104
|
'/api/me/events': ['GET'],
|
|
105
105
|
'/api/me/feed': ['GET'],
|
|
106
|
+
'/api/me/feed/clear-resolved': ['POST'],
|
|
107
|
+
'/api/me/feed/delete': ['POST'],
|
|
106
108
|
'/api/me/feed/read': ['POST'],
|
|
107
109
|
'/api/me/feed/read-all': ['POST'],
|
|
110
|
+
'/api/me/feed/restore': ['POST'],
|
|
111
|
+
'/api/me/feed/unread': ['POST'],
|
|
108
112
|
'/api/me/md': ['GET'],
|
|
109
113
|
'/api/me/meetings/recordings/start': ['POST'],
|
|
110
114
|
'/api/me/meetings/recordings/{id}': ['DELETE'],
|
|
@@ -107,6 +107,54 @@ way. This directory holds that shared spelling.
|
|
|
107
107
|
domain is the largest trust change in the wave, and an empty body is
|
|
108
108
|
indistinguishable from a mistaken or replayed body-less POST.
|
|
109
109
|
|
|
110
|
+
## Relevance
|
|
111
|
+
|
|
112
|
+
Doctrine: control ADR `source-discovery-relevance-funnel`. Vocabulary: ADR
|
|
113
|
+
`source-discovery-relevance-vocabulary`. The sets live in `relevance.ts`.
|
|
114
|
+
|
|
115
|
+
- **Two axes on the wire.**
|
|
116
|
+
- **Relevance** is the funnel's verdict: `visibility` is `shown` or
|
|
117
|
+
`hidden`.
|
|
118
|
+
- **Treatment** is a person's decision: `recommendedUse` (machine state,
|
|
119
|
+
null until classified), `use` (the committed treatment, null until
|
|
120
|
+
chosen) and `selectionSource` (`person` or `approval_default`, present
|
|
121
|
+
exactly when `use` is).
|
|
122
|
+
|
|
123
|
+
Neither axis changes the other. The disclosure question is asked only of
|
|
124
|
+
lines that cleared relevance.
|
|
125
|
+
|
|
126
|
+
- **Hidden is not dropped.** `SOURCE_RELEVANCE_TIERS` includes `dropped` so the
|
|
127
|
+
backend can mirror it in persistence, but the item schema excludes it: a
|
|
128
|
+
dropped sighting never reaches the wire. A hidden line sits behind the
|
|
129
|
+
drawer, is never `included`, and a person can bring it into review with the
|
|
130
|
+
patch `{ visibility: "shown" }`. The patch changes exactly one field, so
|
|
131
|
+
promoting a line never also commits a treatment, and no client can hide a
|
|
132
|
+
line.
|
|
133
|
+
- **Classification is separate from visibility.** `classificationState` is
|
|
134
|
+
`unclassified` (so `sourceClass` and `recommendedUse` are both null) or
|
|
135
|
+
`classified` (both non-null). A shown line can still be
|
|
136
|
+
`pending_classification` in its `machineState`.
|
|
137
|
+
- **Reasons are a discriminated union.** `relevanceReasons` (≤ 8) is
|
|
138
|
+
`RelevanceReason`: `lexicon` (a product-owned `term` and a `count`),
|
|
139
|
+
`signal` (a `value` or null), `evaluation` (a code only) or `model` (a
|
|
140
|
+
`score` or null). Each kind has its own disjoint code set, and the app
|
|
141
|
+
switches over `kind`, then `code`, exhaustively. `relevance` is the fused
|
|
142
|
+
score, and `matchReason` is server copy rendered from the reasons
|
|
143
|
+
(deprecated).
|
|
144
|
+
- **Holds and lifecycle travel on the item.** `personHoldReasons` are the
|
|
145
|
+
field-level barriers a person's actions placed on the line. `machineState`
|
|
146
|
+
is its lifecycle (`fresh`, `refreshed`, `filtered_out`, `not_rediscovered`,
|
|
147
|
+
`pending_classification`, `retired`). `attention` says which lane
|
|
148
|
+
(`exploit` or `explore`) earned each model tier's look.
|
|
149
|
+
- **Attach shows its audience.** `targetAudience` is the audience Attach would
|
|
150
|
+
disclose to. Null means unknown and reads as "may widen".
|
|
151
|
+
- **No single ambiguous total.** `discovery` on the set response carries the
|
|
152
|
+
last completed run's counts (found, retained decisions, not rediscovered,
|
|
153
|
+
pending classification, dropped, coverage and how the scan ended). It is
|
|
154
|
+
null before any run completes. `totals` adds `filesHidden`,
|
|
155
|
+
`filesNotRediscovered` and `filesPendingClassification`, counted by
|
|
156
|
+
`candidateTotalsOf` over distinct files.
|
|
157
|
+
|
|
110
158
|
## Public API
|
|
111
159
|
|
|
112
160
|
- `SOURCE_CLASSES` / `SourceClass` — what kind of document a source is.
|
|
@@ -135,7 +183,16 @@ way. This directory holds that shared spelling.
|
|
|
135
183
|
`candidateTotalsOf(items)` — THE file count: distinct `sourceId`, never lines
|
|
136
184
|
(ADR-CONTRACTS-195).
|
|
137
185
|
- `SourceSyncCandidateItemPatchSchema` / `SourceSyncCandidateItemPatch` — one
|
|
138
|
-
edit to one candidate item
|
|
186
|
+
edit to one candidate item: exactly one of visibility (`shown` only), use,
|
|
187
|
+
class or target.
|
|
188
|
+
- `SOURCE_RELEVANCE_TIERS`, `CLASSIFICATION_STATES`, `RELEVANCE_REASON_KINDS`,
|
|
189
|
+
`LEXICON_REASON_CODES`, `SIGNAL_REASON_CODES`, `EVALUATION_REASON_CODES`,
|
|
190
|
+
`MODEL_REASON_CODES`, `TARGET_AUDIENCES`, `ATTENTION_LANES`,
|
|
191
|
+
`CANDIDATE_MACHINE_STATES`, `SELECTION_SOURCES`, `PERSON_HOLD_REASONS`,
|
|
192
|
+
`SCAN_OUTCOMES` and their derived unions — the relevance vocabulary (see
|
|
193
|
+
Relevance).
|
|
194
|
+
- `RelevanceReasonSchema` / `RelevanceReason` — why a candidate scored as it
|
|
195
|
+
did: a discriminated union on `kind`.
|
|
139
196
|
- `SourceSyncAddCandidateRequestSchema` / `SourceSyncAddCandidateRequest` —
|
|
140
197
|
"add one I missed": a provider file id picked in the provider's own search.
|
|
141
198
|
- `SourceSyncApproveRequestSchema` / `SourceSyncApproveRequest` — approve the
|
|
@@ -210,4 +267,5 @@ way. This directory holds that shared spelling.
|
|
|
210
267
|
|
|
211
268
|
`rules.ts` imports only `zod`, `../api/primitives` and `./vocabulary`.
|
|
212
269
|
`upkeep.ts` and `reach.ts` import only `zod` and `../api/primitives`.
|
|
270
|
+
`relevance.ts` imports only `zod`.
|
|
213
271
|
`vocabulary.ts` itself has no imports.
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* backend answers with.
|
|
6
6
|
*
|
|
7
7
|
* INVARIANTS TESTED:
|
|
8
|
-
* - A candidate patch changes
|
|
9
|
-
* unknown key is an error, not surplus.
|
|
8
|
+
* - A candidate patch changes exactly one thing: an empty patch is refused, and
|
|
9
|
+
* an unknown key is an error, not surplus.
|
|
10
10
|
* - An approval echoes the revision the person reviewed and carries the spend
|
|
11
11
|
* cap as integer micro-USD; either one missing is refused.
|
|
12
12
|
* - An approval answers PER SCOPE: one row per scope approved, each bound to its
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* below, which fails typecheck if one is missing at the root).
|
|
17
17
|
* - The same two checks hold for the Release D names: staleness findings,
|
|
18
18
|
* standing orders and the Drive-reach shapes.
|
|
19
|
+
* - Every runtime name `relevance.ts` declares is reachable from the root as
|
|
20
|
+
* the same binding.
|
|
19
21
|
* - `ACTION_ITEM_KINDS` at the root carries `sourceSync.approval_pending`.
|
|
20
22
|
* - The root re-exports the domain barrel explicitly.
|
|
21
23
|
*
|
|
@@ -66,6 +68,7 @@ import type {
|
|
|
66
68
|
SourceSyncStandingOrdersResponse,
|
|
67
69
|
} from "../../index";
|
|
68
70
|
import * as reach from "../reach";
|
|
71
|
+
import * as relevance from "../relevance";
|
|
69
72
|
import * as rules from "../rules";
|
|
70
73
|
import * as schemas from "../schemas";
|
|
71
74
|
import * as upkeep from "../upkeep";
|
|
@@ -236,6 +239,17 @@ describe("source-sync Release C package surface", () => {
|
|
|
236
239
|
}
|
|
237
240
|
});
|
|
238
241
|
|
|
242
|
+
it("every discovery relevance set is exported from the package root", () => {
|
|
243
|
+
const root = rootBarrel as Record<string, unknown>;
|
|
244
|
+
const names = Object.keys(relevance);
|
|
245
|
+
expect(names.length).toBeGreaterThan(0);
|
|
246
|
+
for (const name of names) {
|
|
247
|
+
expect(root[name], `${name} at the root`).toBe(
|
|
248
|
+
(relevance as Record<string, unknown>)[name],
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
239
253
|
it("exposes sourceSync.approval_pending in ACTION_ITEM_KINDS at the root", () => {
|
|
240
254
|
expect(rootBarrel.ACTION_ITEM_KINDS).toContain(
|
|
241
255
|
"sourceSync.approval_pending",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one file count for a candidate review.
|
|
3
|
+
*
|
|
4
|
+
* INVARIANTS TESTED:
|
|
5
|
+
* - Every total counts DISTINCT files (`sourceId`), never lines: a file routed to
|
|
6
|
+
* two documents is two lines and one file in every total.
|
|
7
|
+
* - A file is hidden only when EVERY line of it is hidden; one shown line shows it.
|
|
8
|
+
* - A file is not rediscovered / pending classification when ANY line of it is.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import { candidateTotalsOf, type CandidateTotalsInput } from "../index";
|
|
13
|
+
|
|
14
|
+
function line(
|
|
15
|
+
sourceId: string,
|
|
16
|
+
overrides: Partial<CandidateTotalsInput> = {},
|
|
17
|
+
): CandidateTotalsInput {
|
|
18
|
+
return {
|
|
19
|
+
sourceId,
|
|
20
|
+
included: true,
|
|
21
|
+
treatmentUnresolved: false,
|
|
22
|
+
visibility: "shown",
|
|
23
|
+
machineState: "fresh",
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("candidateTotalsOf", () => {
|
|
29
|
+
it("counts distinct files, never lines", () => {
|
|
30
|
+
expect(
|
|
31
|
+
candidateTotalsOf([
|
|
32
|
+
line("a", { treatmentUnresolved: true }),
|
|
33
|
+
line("a"),
|
|
34
|
+
line("b", { included: false, treatmentUnresolved: true }),
|
|
35
|
+
line("c"),
|
|
36
|
+
]),
|
|
37
|
+
).toEqual({
|
|
38
|
+
files: 3,
|
|
39
|
+
filesIncluded: 2,
|
|
40
|
+
filesNeedingChoice: 1,
|
|
41
|
+
filesHidden: 0,
|
|
42
|
+
filesNotRediscovered: 0,
|
|
43
|
+
filesPendingClassification: 0,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("a file is hidden only when every line of it is hidden", () => {
|
|
48
|
+
const hidden = { visibility: "hidden", included: false } as const;
|
|
49
|
+
const totals = candidateTotalsOf([
|
|
50
|
+
line("partly", hidden),
|
|
51
|
+
line("partly"),
|
|
52
|
+
line("wholly", hidden),
|
|
53
|
+
line("wholly", hidden),
|
|
54
|
+
line("shown"),
|
|
55
|
+
]);
|
|
56
|
+
expect(totals.files).toBe(3);
|
|
57
|
+
expect(totals.filesHidden).toBe(1);
|
|
58
|
+
expect(totals.filesIncluded).toBe(2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("a file is not rediscovered or pending classification when any line of it is", () => {
|
|
62
|
+
const totals = candidateTotalsOf([
|
|
63
|
+
line("a", { machineState: "not_rediscovered" }),
|
|
64
|
+
line("a", { machineState: "refreshed" }),
|
|
65
|
+
line("b", { machineState: "pending_classification" }),
|
|
66
|
+
line("b"),
|
|
67
|
+
line("c", { machineState: "retired" }),
|
|
68
|
+
]);
|
|
69
|
+
expect(totals.filesNotRediscovered).toBe(1);
|
|
70
|
+
expect(totals.filesPendingClassification).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("a file routed to two documents counts once in every total", () => {
|
|
74
|
+
const facts = {
|
|
75
|
+
treatmentUnresolved: true,
|
|
76
|
+
visibility: "hidden",
|
|
77
|
+
machineState: "not_rediscovered",
|
|
78
|
+
included: false,
|
|
79
|
+
} as const;
|
|
80
|
+
const hiddenTwice = candidateTotalsOf([line("a", facts), line("a", facts)]);
|
|
81
|
+
expect(hiddenTwice).toEqual({
|
|
82
|
+
files: 1,
|
|
83
|
+
filesIncluded: 0,
|
|
84
|
+
filesNeedingChoice: 0,
|
|
85
|
+
filesHidden: 1,
|
|
86
|
+
filesNotRediscovered: 1,
|
|
87
|
+
filesPendingClassification: 0,
|
|
88
|
+
});
|
|
89
|
+
const shownTwice = candidateTotalsOf([
|
|
90
|
+
line("a", {
|
|
91
|
+
treatmentUnresolved: true,
|
|
92
|
+
machineState: "pending_classification",
|
|
93
|
+
}),
|
|
94
|
+
line("a", {
|
|
95
|
+
treatmentUnresolved: true,
|
|
96
|
+
machineState: "pending_classification",
|
|
97
|
+
}),
|
|
98
|
+
]);
|
|
99
|
+
expect(shownTwice).toEqual({
|
|
100
|
+
files: 1,
|
|
101
|
+
filesIncluded: 1,
|
|
102
|
+
filesNeedingChoice: 1,
|
|
103
|
+
filesHidden: 0,
|
|
104
|
+
filesNotRediscovered: 0,
|
|
105
|
+
filesPendingClassification: 1,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("counts nothing for an empty review", () => {
|
|
110
|
+
expect(candidateTotalsOf([])).toEqual({
|
|
111
|
+
files: 0,
|
|
112
|
+
filesIncluded: 0,
|
|
113
|
+
filesNeedingChoice: 0,
|
|
114
|
+
filesHidden: 0,
|
|
115
|
+
filesNotRediscovered: 0,
|
|
116
|
+
filesPendingClassification: 0,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|