@a4anthony/proctorkit-types 0.1.0 → 0.2.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/dist/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export * from "./worker-protocol.js";
5
5
  export * from "./production-safety.js";
6
6
  export * from "./status.js";
7
7
  export * from "./billing.js";
8
+ export * from "./integrity.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -5,4 +5,5 @@ export * from "./worker-protocol.js";
5
5
  export * from "./production-safety.js";
6
6
  export * from "./status.js";
7
7
  export * from "./billing.js";
8
+ export * from "./integrity.js";
8
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,20 @@
1
+ export type IntegrityFindingSeverity = "violation" | "concern" | "info";
2
+ export interface IntegrityCategoryRule {
3
+ key: string;
4
+ label: string;
5
+ tone: string;
6
+ weight: number;
7
+ cap: number;
8
+ severity: IntegrityFindingSeverity;
9
+ match: (kind: string) => boolean;
10
+ }
11
+ /**
12
+ * Canonical integrity-score rules shared by the API, dashboard, and reports.
13
+ * Keep all score consumers on this table so list ordering cannot disagree
14
+ * with the score shown in session review.
15
+ */
16
+ export declare const INTEGRITY_CATEGORIES: ReadonlyArray<IntegrityCategoryRule>;
17
+ export declare function isIntegrityFlag(kind: string): boolean;
18
+ export declare function computeIntegrityScoreFromKinds(kinds: readonly string[]): number;
19
+ export declare function computeIntegrityScoreFromKindCounts(counts: ReadonlyMap<string, number>): number;
20
+ //# sourceMappingURL=integrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.d.ts","sourceRoot":"","sources":["../src/integrity.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAExE,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,wBAAwB,CAAC;IACnC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAClC;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAiFrE,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAM/E;AAED,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAS/F"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Canonical integrity-score rules shared by the API, dashboard, and reports.
3
+ * Keep all score consumers on this table so list ordering cannot disagree
4
+ * with the score shown in session review.
5
+ */
6
+ export const INTEGRITY_CATEGORIES = [
7
+ {
8
+ key: "identity",
9
+ label: "Identity mismatch",
10
+ tone: "#dc2626",
11
+ weight: 40,
12
+ cap: 40,
13
+ severity: "violation",
14
+ match: (kind) => kind.startsWith("face.identity-mismatch"),
15
+ },
16
+ {
17
+ key: "another-person",
18
+ label: "Another person / device",
19
+ tone: "#ef4444",
20
+ weight: 25,
21
+ cap: 35,
22
+ severity: "violation",
23
+ match: (kind) => kind === "face.multiple" ||
24
+ kind === "face.multiple-at-snapshot" ||
25
+ kind.startsWith("webcam.second-device-detected") ||
26
+ kind.startsWith("webcam.second-screen-detected"),
27
+ },
28
+ {
29
+ key: "prohibited-object",
30
+ label: "Prohibited object",
31
+ tone: "#f97316",
32
+ weight: 18,
33
+ cap: 30,
34
+ severity: "violation",
35
+ match: (kind) => kind.startsWith("webcam.phone-detected") || kind.startsWith("webcam.book-detected"),
36
+ },
37
+ {
38
+ key: "evasion",
39
+ label: "Monitoring evasion",
40
+ tone: "#dc2626",
41
+ weight: 12,
42
+ cap: 30,
43
+ severity: "violation",
44
+ match: (kind) => kind === "fullscreen.exited" ||
45
+ kind === "screen-share.declined" ||
46
+ kind === "screen-share.stopped",
47
+ },
48
+ {
49
+ key: "window-switching",
50
+ label: "Window / app switching",
51
+ tone: "#f59e0b",
52
+ weight: 3,
53
+ cap: 20,
54
+ severity: "concern",
55
+ match: (kind) => kind === "screen.window-switched" || kind === "focus.lost" || kind === "tab.hidden",
56
+ },
57
+ {
58
+ key: "face-gaze",
59
+ label: "Face absence / gaze",
60
+ tone: "#f43f5e",
61
+ weight: 5,
62
+ cap: 18,
63
+ severity: "concern",
64
+ match: (kind) => kind === "face.lost" ||
65
+ kind === "face.lost-at-snapshot" ||
66
+ kind === "face.gaze-off-screen" ||
67
+ kind === "face.gaze-off-screen-at-snapshot",
68
+ },
69
+ {
70
+ key: "clipboard",
71
+ label: "Clipboard / screenshot",
72
+ tone: "#fb923c",
73
+ weight: 2,
74
+ cap: 12,
75
+ severity: "concern",
76
+ match: (kind) => kind.startsWith("clipboard.") ||
77
+ kind === "screenshot.attempted" ||
78
+ kind === "keyboard.blocked" ||
79
+ kind === "contextmenu.opened",
80
+ },
81
+ ];
82
+ export function isIntegrityFlag(kind) {
83
+ return INTEGRITY_CATEGORIES.some((category) => category.match(kind));
84
+ }
85
+ export function computeIntegrityScoreFromKinds(kinds) {
86
+ const counts = new Map();
87
+ for (const kind of kinds) {
88
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
89
+ }
90
+ return computeIntegrityScoreFromKindCounts(counts);
91
+ }
92
+ export function computeIntegrityScoreFromKindCounts(counts) {
93
+ const penalty = INTEGRITY_CATEGORIES.reduce((total, category) => {
94
+ let count = 0;
95
+ for (const [kind, occurrences] of counts) {
96
+ if (category.match(kind))
97
+ count += occurrences;
98
+ }
99
+ return total + Math.min(category.cap, category.weight * count);
100
+ }, 0);
101
+ return Math.max(0, Math.min(100, Math.round(100 - penalty)));
102
+ }
103
+ //# sourceMappingURL=integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.js","sourceRoot":"","sources":["../src/integrity.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a4anthony/proctorkit-types",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared TypeScript types for the proctoring platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@ export * from "./worker-protocol.js";
5
5
  export * from "./production-safety.js";
6
6
  export * from "./status.js";
7
7
  export * from "./billing.js";
8
+ export * from "./integrity.js";
@@ -0,0 +1,122 @@
1
+ export type IntegrityFindingSeverity = "violation" | "concern" | "info";
2
+
3
+ export interface IntegrityCategoryRule {
4
+ key: string;
5
+ label: string;
6
+ tone: string;
7
+ weight: number;
8
+ cap: number;
9
+ severity: IntegrityFindingSeverity;
10
+ match: (kind: string) => boolean;
11
+ }
12
+
13
+ /**
14
+ * Canonical integrity-score rules shared by the API, dashboard, and reports.
15
+ * Keep all score consumers on this table so list ordering cannot disagree
16
+ * with the score shown in session review.
17
+ */
18
+ export const INTEGRITY_CATEGORIES: ReadonlyArray<IntegrityCategoryRule> = [
19
+ {
20
+ key: "identity",
21
+ label: "Identity mismatch",
22
+ tone: "#dc2626",
23
+ weight: 40,
24
+ cap: 40,
25
+ severity: "violation",
26
+ match: (kind) => kind.startsWith("face.identity-mismatch"),
27
+ },
28
+ {
29
+ key: "another-person",
30
+ label: "Another person / device",
31
+ tone: "#ef4444",
32
+ weight: 25,
33
+ cap: 35,
34
+ severity: "violation",
35
+ match: (kind) =>
36
+ kind === "face.multiple" ||
37
+ kind === "face.multiple-at-snapshot" ||
38
+ kind.startsWith("webcam.second-device-detected") ||
39
+ kind.startsWith("webcam.second-screen-detected"),
40
+ },
41
+ {
42
+ key: "prohibited-object",
43
+ label: "Prohibited object",
44
+ tone: "#f97316",
45
+ weight: 18,
46
+ cap: 30,
47
+ severity: "violation",
48
+ match: (kind) =>
49
+ kind.startsWith("webcam.phone-detected") || kind.startsWith("webcam.book-detected"),
50
+ },
51
+ {
52
+ key: "evasion",
53
+ label: "Monitoring evasion",
54
+ tone: "#dc2626",
55
+ weight: 12,
56
+ cap: 30,
57
+ severity: "violation",
58
+ match: (kind) =>
59
+ kind === "fullscreen.exited" ||
60
+ kind === "screen-share.declined" ||
61
+ kind === "screen-share.stopped",
62
+ },
63
+ {
64
+ key: "window-switching",
65
+ label: "Window / app switching",
66
+ tone: "#f59e0b",
67
+ weight: 3,
68
+ cap: 20,
69
+ severity: "concern",
70
+ match: (kind) =>
71
+ kind === "screen.window-switched" || kind === "focus.lost" || kind === "tab.hidden",
72
+ },
73
+ {
74
+ key: "face-gaze",
75
+ label: "Face absence / gaze",
76
+ tone: "#f43f5e",
77
+ weight: 5,
78
+ cap: 18,
79
+ severity: "concern",
80
+ match: (kind) =>
81
+ kind === "face.lost" ||
82
+ kind === "face.lost-at-snapshot" ||
83
+ kind === "face.gaze-off-screen" ||
84
+ kind === "face.gaze-off-screen-at-snapshot",
85
+ },
86
+ {
87
+ key: "clipboard",
88
+ label: "Clipboard / screenshot",
89
+ tone: "#fb923c",
90
+ weight: 2,
91
+ cap: 12,
92
+ severity: "concern",
93
+ match: (kind) =>
94
+ kind.startsWith("clipboard.") ||
95
+ kind === "screenshot.attempted" ||
96
+ kind === "keyboard.blocked" ||
97
+ kind === "contextmenu.opened",
98
+ },
99
+ ];
100
+
101
+ export function isIntegrityFlag(kind: string): boolean {
102
+ return INTEGRITY_CATEGORIES.some((category) => category.match(kind));
103
+ }
104
+
105
+ export function computeIntegrityScoreFromKinds(kinds: readonly string[]): number {
106
+ const counts = new Map<string, number>();
107
+ for (const kind of kinds) {
108
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
109
+ }
110
+ return computeIntegrityScoreFromKindCounts(counts);
111
+ }
112
+
113
+ export function computeIntegrityScoreFromKindCounts(counts: ReadonlyMap<string, number>): number {
114
+ const penalty = INTEGRITY_CATEGORIES.reduce((total, category) => {
115
+ let count = 0;
116
+ for (const [kind, occurrences] of counts) {
117
+ if (category.match(kind)) count += occurrences;
118
+ }
119
+ return total + Math.min(category.cap, category.weight * count);
120
+ }, 0);
121
+ return Math.max(0, Math.min(100, Math.round(100 - penalty)));
122
+ }