@company-semantics/contracts 89.2.0 → 90.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "89.2.0",
3
+ "version": "90.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -109,6 +109,7 @@
109
109
  "guard:decisions-deprecation:json": "npx tsx scripts/ci/decisions-deprecation-guard.ts --json",
110
110
  "guard:quick": "sh scripts/ci/run-guard.sh --quick",
111
111
  "ci:local": "pnpm typecheck && pnpm lint:md && pnpm guard:test && pnpm guard",
112
+ "verify": "sh scripts/ci/run-verify.sh",
112
113
  "guard": "sh scripts/ci/run-guard.sh",
113
114
  "guard:test": "NODE_OPTIONS='--max-old-space-size=4096' vitest run scripts/ci/__tests__",
114
115
  "release": "npx tsx scripts/release.ts",
@@ -183,7 +184,7 @@
183
184
  }
184
185
  },
185
186
  "lint-staged": {
186
- "*.ts": "bash -c 'tsc -b --noEmit'",
187
+ "*.ts": "sh scripts/ci/unless-verified.sh tsc 'tsc -b --noEmit'",
187
188
  "*.md": "markdownlint-cli2",
188
189
  "package.json": "node -e \"JSON.parse(require('fs').readFileSync('package.json'))\""
189
190
  },
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '7144c9a832d5' as const;
3
- export const SPEC_HASH_FULL = '7144c9a832d55cf9179e5825c6c8fa486bf7130c0d1022d1b721f175dcf99f6d' as const;
2
+ export const SPEC_HASH = 'ba43b4881a93' as const;
3
+ export const SPEC_HASH_FULL = 'ba43b4881a9318819726dfe0c0465ecae3802951f7f558fec6786a7171195a91' as const;
@@ -9328,8 +9328,10 @@ export interface components {
9328
9328
  /** @enum {string} */
9329
9329
  classificationState: "unclassified" | "classified";
9330
9330
  sourceClass: ("board_deck" | "board_minutes" | "investor_update" | "financial_brief" | "budget_forecast" | "okr_plan" | "annual_plan" | "all_hands" | "unit_strategy" | "roadmap" | "qbr" | "other" | "decoy") | null;
9331
- documentType: ("board_deck" | "board_minutes" | "strategy_memo" | "planning_doc" | "okr_doc" | "roadmap" | "operating_review" | "all_hands_deck" | "offsite_notes" | "investor_update" | "pitch_deck" | "product_spec" | "research_analysis" | "general_meeting_notes" | "financial_plan" | "financial_actuals" | "equity_admin_record" | "corporate_formation_record" | "corporate_governance_record" | "government_filing" | "equity_agreement" | "commercial_agreement" | "employment_agreement" | "transaction_record" | "personal_document" | "social_event_material" | "coursework" | "empty_template" | "image_or_screenshot" | "unknown") | null;
9331
+ documentType: ("board_deck" | "board_minutes" | "strategy_memo" | "planning_doc" | "okr_doc" | "roadmap" | "operating_review" | "all_hands_deck" | "offsite_notes" | "investor_update" | "pitch_deck" | "product_spec" | "research_analysis" | "general_meeting_notes" | "financial_plan" | "financial_actuals" | "equity_admin_record" | "corporate_formation_record" | "corporate_governance_record" | "government_filing" | "equity_agreement" | "commercial_agreement" | "employment_agreement" | "transaction_record" | "personal_document" | "social_event_material" | "coursework" | "empty_template" | "image_or_screenshot" | "team_charter" | "process_playbook" | "policy_handbook" | "onboarding_guide" | "values_culture" | "positioning_messaging" | "decision_log" | "postmortem" | "metrics_definition" | "operating_cadence" | "unknown") | null;
9332
9332
  documentTypeConfidence: number | null;
9333
+ operatingKnowledgeFamily: ("direction" | "operating_model" | "practices" | "identity_market" | "decisions_learning") | null;
9334
+ canonicalPath: string | null;
9333
9335
  periodLabel: string | null;
9334
9336
  seriesRole: ("current" | "history") | null;
9335
9337
  relevance: number;
@@ -9375,6 +9377,59 @@ export interface components {
9375
9377
  t1: ("exploit" | "explore") | null;
9376
9378
  t2: ("exploit" | "explore") | null;
9377
9379
  };
9380
+ attentionEvidence: ({
9381
+ /** @constant */
9382
+ kind: "tree_prior";
9383
+ score: number;
9384
+ folderId: string;
9385
+ source: {
9386
+ producer: string;
9387
+ version: string;
9388
+ };
9389
+ } | {
9390
+ /** @constant */
9391
+ kind: "path_terms";
9392
+ values: string[];
9393
+ source: {
9394
+ producer: string;
9395
+ version: string;
9396
+ };
9397
+ } | {
9398
+ /** @constant */
9399
+ kind: "content_match";
9400
+ values: string[];
9401
+ strength: number;
9402
+ probeId: string;
9403
+ source: {
9404
+ producer: string;
9405
+ version: string;
9406
+ };
9407
+ } | {
9408
+ /** @constant */
9409
+ kind: "breadth";
9410
+ /** @enum {string} */
9411
+ value: "restricted_few" | "group" | "domain" | "anyone" | "unknown";
9412
+ source: {
9413
+ producer: string;
9414
+ version: string;
9415
+ };
9416
+ } | {
9417
+ /** @constant */
9418
+ kind: "recency";
9419
+ score: number;
9420
+ source: {
9421
+ producer: string;
9422
+ version: string;
9423
+ };
9424
+ } | {
9425
+ /** @constant */
9426
+ kind: "mime_prior";
9427
+ score: number;
9428
+ source: {
9429
+ producer: string;
9430
+ version: string;
9431
+ };
9432
+ })[] | null;
9378
9433
  included: boolean;
9379
9434
  treatmentUnresolved: boolean;
9380
9435
  matchReason: string;
@@ -9465,6 +9520,33 @@ export interface components {
9465
9520
  coveragePercent: number;
9466
9521
  /** @enum {string} */
9467
9522
  scanOutcome: "complete" | "budget_exhausted" | "file_ceiling" | "stopped";
9523
+ enumerationCompleteness: ("complete" | "local_budget_truncated" | "provider_incomplete" | "both") | null;
9524
+ rootCoverage: {
9525
+ /** @enum {string} */
9526
+ rootKind: "user" | "shared_drive" | "domain";
9527
+ rootId: string | null;
9528
+ /** @enum {string} */
9529
+ status: "complete" | "local_budget_truncated" | "provider_incomplete" | "both";
9530
+ enumeratedCount: number;
9531
+ observedOldestModifiedTime: string | null;
9532
+ observedNewestModifiedTime: string | null;
9533
+ skippedRanges: {
9534
+ /** Format: date-time */
9535
+ from: string;
9536
+ /** Format: date-time */
9537
+ to: string;
9538
+ }[];
9539
+ }[] | null;
9540
+ stageCounters: {
9541
+ filesEnumerated: number;
9542
+ filesMetadataEligible: number;
9543
+ filesT0Scored: number;
9544
+ filesT1Attended: number;
9545
+ filesPeeked: number;
9546
+ filesClassified: number;
9547
+ filesShown: number;
9548
+ filesRetained: number;
9549
+ } | null;
9468
9550
  } | null;
9469
9551
  };
9470
9552
  SourceSyncApproveResponse: {
@@ -165,7 +165,15 @@ product policy in the backend decides what that means.
165
165
  what a file IS — `corporate_formation_record`, never `irrelevant_doc` —
166
166
  never how relevant it is. A medium is not a meaning: `image_or_screenshot`
167
167
  is graded on its content. `unknown` is a real answer (the model could not
168
- tell, or two reads disagreed) and is always last.
168
+ tell, or two reads disagreed) and is always last. There are forty: the ten
169
+ operating-knowledge types (`team_charter` … `operating_cadence`, control ADR
170
+ `index-first-operating-knowledge-discovery`) sit just before `unknown`.
171
+ - **Operating-knowledge family is a derived name.**
172
+ `OPERATING_KNOWLEDGE_FAMILIES` (`direction`, `operating_model`, `practices`,
173
+ `identity_market`, `decisions_learning`) is resolved by backend policy from
174
+ the resolved type, like `sourceClass`, and travels as the item's
175
+ `operatingKnowledgeFamily`, which needs a known type. It is not the backend's
176
+ relevance-policy family, which never travels.
169
177
  - **The resolved type travels, nothing else.** The candidate item's
170
178
  `documentType` and `documentTypeConfidence` (0..1) carry the RESOLVED type,
171
179
  after the backend reconciles the T1 and T2 observations. Both are null on an
@@ -191,6 +199,63 @@ product policy in the backend decides what that means.
191
199
  is uncertainty, never irrelevance. `peek_confirms` / `peek_contradicts`
192
200
  stay for v1-pinned rows.
193
201
 
202
+ ## Enumeration
203
+
204
+ Doctrine: control ADR `index-first-operating-knowledge-discovery`. Vocabulary:
205
+ ADR `operating-knowledge-discovery-vocabulary`. The sets live in
206
+ `enumeration.ts`.
207
+
208
+ - **A partial enumeration is never presented as an index of the corpus.**
209
+ `ENUMERATION_COMPLETENESS` has four values, never a boolean: `complete`,
210
+ `local_budget_truncated` (our ceiling), `provider_incomplete` (the
211
+ provider's incomplete search) and `both`. Only `complete` may be called a
212
+ full index. Any other value means absence is unknown, not negative evidence
213
+ (INV-SOURCESYNC-PARTIAL-ENUMERATION-IS-UNKNOWN).
214
+ - **Per root, never by name.** `RootCoverage` covers one root
215
+ (`ENUMERATION_ROOT_KINDS`: `user`, `shared_drive`, `domain`). It holds the
216
+ shared drive's provider id (null for the other kinds), the root's status and
217
+ count, the observed modified-time bounds (present exactly when it enumerated
218
+ a file) and the skipped ranges (none when it is complete). It never holds a
219
+ drive, folder or file name.
220
+ - **The run's completeness is the combination of its roots.**
221
+ `combineEnumerationCompleteness` is pure: our ceiling anywhere and the
222
+ provider's incompleteness anywhere make `both`, and the run is `complete`
223
+ only when every root is.
224
+ - **Counters name what they did.** `DiscoveryStageCounters` holds
225
+ `filesEnumerated`, `filesMetadataEligible`, `filesT0Scored`,
226
+ `filesT1Attended`, `filesPeeked`, `filesClassified`, `filesShown` and
227
+ `filesRetained`, and the counts narrow from enumerated to shown. A metadata
228
+ count never reads as a content read: there is no `filesIndexed`.
229
+ - **On the discovery summary, all or none.** `enumerationCompleteness`,
230
+ `rootCoverage` (1..64) and `stageCounters` are required and nullable. Either
231
+ all three are present, or all three are null, which means the run predates
232
+ index-first discovery.
233
+
234
+ ## Attention evidence
235
+
236
+ Vocabulary: ADR `operating-knowledge-discovery-vocabulary`. The schemas live
237
+ in `attention-evidence.ts`.
238
+
239
+ - **Why a file got T1 attention.** `AttentionEvidence` is an array of at most
240
+ six signals with at most one per `ATTENTION_SIGNAL_KINDS` kind: `tree_prior`
241
+ (a score and the folder's provider `folderId`), `path_terms` (matched
242
+ product terms), `content_match` (matched plan terms, a strength and the
243
+ `probeId`), `breadth` (a `SHARING_BREADTHS` member, so there is no new
244
+ breadth vocabulary), `recency` and `mime_prior` (a score each).
245
+ - **Every signal has provenance.** Each signal carries
246
+ `source: { producer, version }`
247
+ (INV-SOURCESYNC-ATTENTION-EVIDENCE-HAS-PROVENANCE). `producer` is a
248
+ documented lowercase key, not a closed set. The documented keys are
249
+ `lexicon`, `source-discovery-tree-triage`, `source-discovery-plan` and
250
+ `metadata-scorer`.
251
+ - **Terms are vocabulary, never content.** A term is product or plan
252
+ vocabulary that matched. It is never text read from the file and never a
253
+ slice of its name. A folder is named only by its provider id.
254
+ - **Only after T1 attention, never when withheld.** The item's
255
+ `attentionEvidence` and `canonicalPath` (the Tier-1 path) are null when the
256
+ line never received T1 attention, when it is withheld, and for runs before
257
+ index-first discovery.
258
+
194
259
  ## Public API
195
260
 
196
261
  - `SOURCE_CLASSES` / `SourceClass` — what kind of document a source is.
@@ -210,12 +275,34 @@ product policy in the backend decides what that means.
210
275
  `org_admin` or `unit_head`.
211
276
  - `DOCUMENT_TYPES` / `DocumentType` / `KnownDocumentType` — what a source is,
212
277
  structurally; `KnownDocumentType` excludes `unknown` (see Document type).
278
+ - `OPERATING_KNOWLEDGE_FAMILIES` / `OperatingKnowledgeFamily` — what kind of
279
+ operating knowledge a line is, resolved by the backend (see Document type).
280
+ - `ENUMERATION_COMPLETENESS` / `EnumerationCompleteness` — how completely a
281
+ run or root was enumerated; only `complete` is a full index (see
282
+ Enumeration).
283
+ - `ENUMERATION_ROOT_KINDS` / `EnumerationRootKind` — `user`, `shared_drive` or
284
+ `domain`.
285
+ - `combineEnumerationCompleteness(statuses)` — the pure fold of per-root
286
+ statuses into one.
287
+ - `EnumerationSkippedRangeSchema` — a modified-time range left unenumerated.
288
+ - `RootCoverageSchema` / `RootCoverage` — how far one root's enumeration got.
289
+ - `DiscoveryStageCountersSchema` / `DiscoveryStageCounters` — what each stage
290
+ did, by count, narrowing from enumerated to shown.
291
+ - `ATTENTION_SIGNAL_KINDS` / `AttentionSignalKind` — the kinds of attention
292
+ signal (see Attention evidence).
293
+ - `AttentionSignalSourceSchema` / `AttentionSignalSource` — a signal's
294
+ producer and version.
295
+ - `AttentionSignalSchema` / `AttentionSignal` — one signal: a discriminated
296
+ union on `kind`.
297
+ - `AttentionEvidenceSchema` / `AttentionEvidence` — why a line got T1
298
+ attention: at most one signal per kind.
213
299
  - `SourceSyncDiscoveryRunResponseSchema` / `SourceSyncDiscoveryRunResponse` —
214
300
  the discovery run started, and the workflow narrating it.
215
301
  - `SourceSyncCandidateItemSchema` / `SourceSyncCandidateItem` — one source
216
302
  proposed for one target document, with the server's `included` and the
217
303
  resolved `documentType` / `documentTypeConfidence` (null when unclassified
218
- or v1-pinned).
304
+ or v1-pinned), `operatingKnowledgeFamily`, `canonicalPath` and
305
+ `attentionEvidence` (null before index-first discovery).
219
306
  - `SourceSyncCandidateSetResponseSchema` / `SourceSyncCandidateSetResponse` —
220
307
  the draft set under review: its revision, items, presented questions,
221
308
  distinct-file `totals` and cost estimate.
@@ -307,7 +394,10 @@ product policy in the backend decides what that means.
307
394
 
308
395
  `rules.ts` imports only `zod`, `../api/primitives` and `./vocabulary`.
309
396
  `upkeep.ts` and `reach.ts` import only `zod` and `../api/primitives`.
310
- `candidate-item.ts` imports only `zod`, `../api/primitives`, `./vocabulary`
311
- and `./relevance`.
312
- `relevance.ts` imports only `zod`.
397
+ `candidate-item.ts` imports only `zod`, `../api/primitives`, `./vocabulary`,
398
+ `./relevance` and `./attention-evidence`.
399
+ `relevance.ts` imports only `zod` and `./enumeration`.
400
+ `enumeration.ts` imports only `zod` and `../api/primitives` (never
401
+ `./relevance`, so there is no cycle).
402
+ `attention-evidence.ts` imports only `zod` and `./vocabulary`.
313
403
  `vocabulary.ts` itself has no imports.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Attention evidence tests.
3
+ *
4
+ * INVARIANTS TESTED:
5
+ * - Every attention signal kind is accepted with its provenance; empty evidence
6
+ * is accepted
7
+ * - An attention signal kind appears at most once
8
+ * - A tree prior names its folder by provider id; a content match names its probe
9
+ * - Every signal carries a lowercase producer key and a version
10
+ * - Breadth reuses the sharing breadth vocabulary
11
+ */
12
+
13
+ import { describe, it, expect } from "vitest";
14
+
15
+ import { AttentionEvidenceSchema } from "../attention-evidence";
16
+
17
+ const source = { producer: "lexicon", version: "3" };
18
+ const everyKind = [
19
+ {
20
+ kind: "tree_prior",
21
+ score: 0.72,
22
+ folderId: "0AbcFolder",
23
+ source: { producer: "source-discovery-tree-triage", version: "1" },
24
+ },
25
+ { kind: "path_terms", values: ["planning", "qbr"], source },
26
+ {
27
+ kind: "content_match",
28
+ values: ["operating review"],
29
+ strength: 0.8,
30
+ probeId: "practices_1",
31
+ source: { producer: "source-discovery-plan", version: "2" },
32
+ },
33
+ {
34
+ kind: "breadth",
35
+ value: "domain",
36
+ source: { producer: "metadata-scorer", version: "4" },
37
+ },
38
+ {
39
+ kind: "recency",
40
+ score: 0.31,
41
+ source: { producer: "metadata-scorer", version: "4" },
42
+ },
43
+ {
44
+ kind: "mime_prior",
45
+ score: 0.6,
46
+ source: { producer: "metadata-scorer", version: "4" },
47
+ },
48
+ ] as const;
49
+
50
+ describe("AttentionEvidenceSchema", () => {
51
+ it("accepts one entry of every attention signal kind with its provenance", () => {
52
+ expect(AttentionEvidenceSchema.safeParse(everyKind).success).toBe(true);
53
+ expect(AttentionEvidenceSchema.safeParse([]).success).toBe(true);
54
+ });
55
+
56
+ it("an attention signal kind appears at most once", () => {
57
+ expect(
58
+ AttentionEvidenceSchema.safeParse([everyKind[1], everyKind[1]]).success,
59
+ ).toBe(false);
60
+ });
61
+
62
+ it("a tree prior names the folder and a content match names the probe", () => {
63
+ const { folderId: _f, ...noFolder } = everyKind[0];
64
+ const { probeId: _p, ...noProbe } = everyKind[2];
65
+ expect(AttentionEvidenceSchema.safeParse([noFolder]).success).toBe(false);
66
+ expect(AttentionEvidenceSchema.safeParse([noProbe]).success).toBe(false);
67
+ });
68
+
69
+ it("refuses an attention signal without a producer version", () => {
70
+ expect(
71
+ AttentionEvidenceSchema.safeParse([
72
+ {
73
+ kind: "recency",
74
+ score: 0.3,
75
+ source: { producer: "metadata-scorer" },
76
+ },
77
+ ]).success,
78
+ ).toBe(false);
79
+ expect(
80
+ AttentionEvidenceSchema.safeParse([
81
+ {
82
+ kind: "recency",
83
+ score: 0.3,
84
+ source: { producer: "Has Spaces", version: "1" },
85
+ },
86
+ ]).success,
87
+ ).toBe(false);
88
+ });
89
+
90
+ it("breadth reuses the sharing breadth vocabulary", () => {
91
+ expect(
92
+ AttentionEvidenceSchema.safeParse([{ ...everyKind[3], value: "public" }])
93
+ .success,
94
+ ).toBe(false);
95
+ });
96
+ });
@@ -20,6 +20,9 @@
20
20
  * the same binding.
21
21
  * - `DOCUMENT_TYPES` is reachable from the root as the same binding, and its
22
22
  * two types are named by the barrel.
23
+ * - Every index-first discovery name (operating-knowledge families, the
24
+ * enumeration vocabulary, attention evidence) is reachable from the root as
25
+ * the same binding, and its types are named by the barrel.
23
26
  * - `ACTION_ITEM_KINDS` at the root carries `sourceSync.approval_pending`.
24
27
  * - The root re-exports the domain barrel explicitly.
25
28
  *
@@ -69,7 +72,9 @@ import type {
69
72
  SourceSyncStandingOrder,
70
73
  SourceSyncStandingOrdersResponse,
71
74
  } from "../../index";
75
+ import * as attentionEvidence from "../attention-evidence";
72
76
  import * as candidateItem from "../candidate-item";
77
+ import * as enumeration from "../enumeration";
73
78
  import * as reach from "../reach";
74
79
  import * as relevance from "../relevance";
75
80
  import * as rules from "../rules";
@@ -260,6 +265,40 @@ describe("source-sync Release C package surface", () => {
260
265
  expect([...barrelTypeExports]).toContain("KnownDocumentType");
261
266
  });
262
267
 
268
+ it("every index-first discovery name is reachable from the root as the same binding", () => {
269
+ const root = rootBarrel as Record<string, unknown>;
270
+ for (const [name, module] of [
271
+ ["OPERATING_KNOWLEDGE_FAMILIES", vocabulary],
272
+ ["ENUMERATION_COMPLETENESS", enumeration],
273
+ ["ENUMERATION_ROOT_KINDS", enumeration],
274
+ ["combineEnumerationCompleteness", enumeration],
275
+ ["EnumerationSkippedRangeSchema", enumeration],
276
+ ["RootCoverageSchema", enumeration],
277
+ ["DiscoveryStageCountersSchema", enumeration],
278
+ ["ATTENTION_SIGNAL_KINDS", attentionEvidence],
279
+ ["AttentionSignalSourceSchema", attentionEvidence],
280
+ ["AttentionSignalSchema", attentionEvidence],
281
+ ["AttentionEvidenceSchema", attentionEvidence],
282
+ ] as const) {
283
+ const declared = (module as Record<string, unknown>)[name];
284
+ expect(declared, `${name} is declared`).toBeDefined();
285
+ expect(root[name], `${name} at the root`).toBe(declared);
286
+ }
287
+ for (const type of [
288
+ "OperatingKnowledgeFamily",
289
+ "EnumerationCompleteness",
290
+ "EnumerationRootKind",
291
+ "RootCoverage",
292
+ "DiscoveryStageCounters",
293
+ "AttentionSignalKind",
294
+ "AttentionSignalSource",
295
+ "AttentionSignal",
296
+ "AttentionEvidence",
297
+ ]) {
298
+ expect([...barrelTypeExports]).toContain(type);
299
+ }
300
+ });
301
+
263
302
  it("exposes sourceSync.approval_pending in ACTION_ITEM_KINDS at the root", () => {
264
303
  expect(rootBarrel.ACTION_ITEM_KINDS).toContain(
265
304
  "sourceSync.approval_pending",
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Index-first enumeration vocabulary tests.
3
+ *
4
+ * INVARIANTS TESTED:
5
+ * - Root statuses combine so that only every root complete is `complete`, and
6
+ * our ceiling plus the provider's incompleteness is `both`
7
+ * - A complete root skips nothing; a root id names exactly a shared drive
8
+ * - A skipped range never ends before it starts
9
+ * - Observed modified times are present exactly when a root enumerated a file
10
+ * - Stage counters are non-negative and narrow from enumerated to shown
11
+ */
12
+
13
+ import { describe, it, expect } from "vitest";
14
+
15
+ import {
16
+ combineEnumerationCompleteness,
17
+ DiscoveryStageCountersSchema,
18
+ EnumerationSkippedRangeSchema,
19
+ RootCoverageSchema,
20
+ } from "../enumeration";
21
+
22
+ function root(overrides: Record<string, unknown> = {}) {
23
+ return {
24
+ rootKind: "user",
25
+ rootId: null,
26
+ status: "complete",
27
+ enumeratedCount: 3,
28
+ observedOldestModifiedTime: "2025-01-01T00:00:00.000Z",
29
+ observedNewestModifiedTime: "2026-09-30T00:00:00.000Z",
30
+ skippedRanges: [],
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ function counters(overrides: Record<string, unknown> = {}) {
36
+ return {
37
+ filesEnumerated: 1000,
38
+ filesMetadataEligible: 800,
39
+ filesT0Scored: 600,
40
+ filesT1Attended: 120,
41
+ filesPeeked: 40,
42
+ filesClassified: 200,
43
+ filesShown: 25,
44
+ filesRetained: 30,
45
+ ...overrides,
46
+ };
47
+ }
48
+
49
+ const RANGE = {
50
+ from: "2024-01-01T00:00:00.000Z",
51
+ to: "2024-06-30T00:00:00.000Z",
52
+ };
53
+
54
+ describe("enumeration", () => {
55
+ it("combines root statuses into one enumeration completeness", () => {
56
+ expect(combineEnumerationCompleteness(["complete", "complete"])).toBe(
57
+ "complete",
58
+ );
59
+ expect(
60
+ combineEnumerationCompleteness(["complete", "local_budget_truncated"]),
61
+ ).toBe("local_budget_truncated");
62
+ expect(
63
+ combineEnumerationCompleteness(["provider_incomplete", "complete"]),
64
+ ).toBe("provider_incomplete");
65
+ expect(
66
+ combineEnumerationCompleteness([
67
+ "local_budget_truncated",
68
+ "provider_incomplete",
69
+ ]),
70
+ ).toBe("both");
71
+ expect(combineEnumerationCompleteness(["both"])).toBe("both");
72
+ });
73
+
74
+ it("a complete root skips nothing", () => {
75
+ expect(RootCoverageSchema.safeParse(root()).success).toBe(true);
76
+ expect(
77
+ RootCoverageSchema.safeParse(root({ skippedRanges: [RANGE] })).success,
78
+ ).toBe(false);
79
+ expect(
80
+ RootCoverageSchema.safeParse(
81
+ root({ status: "local_budget_truncated", skippedRanges: [RANGE] }),
82
+ ).success,
83
+ ).toBe(true);
84
+ });
85
+
86
+ it("a root id names exactly a shared drive", () => {
87
+ expect(
88
+ RootCoverageSchema.safeParse(root({ rootId: "0AbCdEf" })).success,
89
+ ).toBe(false);
90
+ expect(
91
+ RootCoverageSchema.safeParse(root({ rootKind: "shared_drive" })).success,
92
+ ).toBe(false);
93
+ expect(
94
+ RootCoverageSchema.safeParse(
95
+ root({ rootKind: "shared_drive", rootId: "0AbCdEf" }),
96
+ ).success,
97
+ ).toBe(true);
98
+ expect(
99
+ RootCoverageSchema.safeParse(root({ rootKind: "domain" })).success,
100
+ ).toBe(true);
101
+ });
102
+
103
+ it("refuses a skipped range that ends before it starts", () => {
104
+ expect(EnumerationSkippedRangeSchema.safeParse(RANGE).success).toBe(true);
105
+ expect(
106
+ EnumerationSkippedRangeSchema.safeParse({
107
+ from: RANGE.to,
108
+ to: RANGE.from,
109
+ }).success,
110
+ ).toBe(false);
111
+ });
112
+
113
+ it("an empty root has no observed modified times", () => {
114
+ expect(
115
+ RootCoverageSchema.safeParse(root({ enumeratedCount: 0 })).success,
116
+ ).toBe(false);
117
+ expect(
118
+ RootCoverageSchema.safeParse(
119
+ root({
120
+ enumeratedCount: 0,
121
+ observedOldestModifiedTime: null,
122
+ observedNewestModifiedTime: null,
123
+ }),
124
+ ).success,
125
+ ).toBe(true);
126
+ expect(
127
+ RootCoverageSchema.safeParse(root({ observedNewestModifiedTime: null }))
128
+ .success,
129
+ ).toBe(false);
130
+ expect(
131
+ RootCoverageSchema.safeParse(
132
+ root({
133
+ observedOldestModifiedTime: "2026-09-30T00:00:00.000Z",
134
+ observedNewestModifiedTime: "2025-01-01T00:00:00.000Z",
135
+ }),
136
+ ).success,
137
+ ).toBe(false);
138
+ });
139
+
140
+ it("stage counters narrow from enumerated to shown", () => {
141
+ expect(DiscoveryStageCountersSchema.safeParse(counters()).success).toBe(
142
+ true,
143
+ );
144
+ expect(
145
+ DiscoveryStageCountersSchema.safeParse(counters({ filesPeeked: 121 }))
146
+ .success,
147
+ ).toBe(false);
148
+ expect(
149
+ DiscoveryStageCountersSchema.safeParse(counters({ filesShown: 31 }))
150
+ .success,
151
+ ).toBe(false);
152
+ });
153
+
154
+ it("refuses negative stage counters", () => {
155
+ expect(
156
+ DiscoveryStageCountersSchema.safeParse(counters({ filesEnumerated: -1 }))
157
+ .success,
158
+ ).toBe(false);
159
+ });
160
+ });