@company-semantics/contracts 89.2.0 → 91.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.
Files changed (53) hide show
  1. package/package.json +4 -3
  2. package/src/affordances/README.md +65 -0
  3. package/src/affordances/__tests__/README.md +30 -0
  4. package/src/affordances/__tests__/affordances.test.ts +251 -0
  5. package/src/affordances/affordance.ts +124 -0
  6. package/src/affordances/args.ts +59 -0
  7. package/src/affordances/availability.ts +78 -0
  8. package/src/affordances/ids.ts +45 -0
  9. package/src/affordances/index.ts +39 -0
  10. package/src/api/generated-spec-hash.ts +2 -2
  11. package/src/api/generated.ts +142 -2
  12. package/src/generated/openapi-routes.ts +1 -0
  13. package/src/proactive/README.md +26 -4
  14. package/src/proactive/__tests__/README.md +10 -0
  15. package/src/proactive/__tests__/chat-templates.test.ts +8 -0
  16. package/src/proactive/__tests__/vocabulary.test.ts +11 -0
  17. package/src/proactive/__tests__/workflow-attention-fixtures.ts +49 -0
  18. package/src/proactive/__tests__/workflow-attention.test.ts +146 -0
  19. package/src/proactive/composer.ts +8 -0
  20. package/src/proactive/facts.ts +36 -0
  21. package/src/proactive/index.ts +2 -0
  22. package/src/proactive/kinds.ts +29 -0
  23. package/src/proactive/registry.ts +22 -0
  24. package/src/proactive/surfaces.ts +2 -0
  25. package/src/proactive/templates/README.md +9 -1
  26. package/src/proactive/templates/index.ts +2 -0
  27. package/src/proactive/templates/workflow-needs-attention.ts +35 -0
  28. package/src/root-exports/README.md +1 -0
  29. package/src/root-exports/delivery.ts +69 -1
  30. package/src/source-sync/README.md +95 -5
  31. package/src/source-sync/__tests__/attention-evidence.test.ts +96 -0
  32. package/src/source-sync/__tests__/barrel.test.ts +39 -0
  33. package/src/source-sync/__tests__/enumeration.test.ts +160 -0
  34. package/src/source-sync/__tests__/schemas.test.ts +223 -0
  35. package/src/source-sync/__tests__/vocabulary.test.ts +104 -5
  36. package/src/source-sync/attention-evidence.ts +93 -0
  37. package/src/source-sync/candidate-item.ts +25 -0
  38. package/src/source-sync/enumeration.ts +152 -0
  39. package/src/source-sync/index.ts +35 -1
  40. package/src/source-sync/relevance.ts +85 -11
  41. package/src/source-sync/vocabulary.ts +37 -1
  42. package/src/user-notifications/README.md +8 -0
  43. package/src/user-notifications/kinds.ts +13 -0
  44. package/src/user-notifications/lifecycle-registry.ts +10 -0
  45. package/src/workflows/README.md +38 -2
  46. package/src/workflows/__tests__/README.md +21 -2
  47. package/src/workflows/__tests__/affordances-of-outcome.test.ts +99 -0
  48. package/src/workflows/__tests__/deferred-route.test.ts +168 -0
  49. package/src/workflows/__tests__/outcome.test.ts +66 -0
  50. package/src/workflows/affordances-of-outcome.ts +65 -0
  51. package/src/workflows/deferred-route.ts +240 -0
  52. package/src/workflows/index.ts +34 -0
  53. package/src/workflows/outcome.ts +114 -1
@@ -23,7 +23,9 @@
23
23
  * receive different prose because they were handed different facts.
24
24
  */
25
25
 
26
+ import type { WorkflowOutcomeData } from "../message-parts/workflow-outcome";
26
27
  import type { OrgSharedTransitionTrigger } from "../org/shared-transition";
28
+ import type { ExplainedWorkflowOutcome } from "../workflows/deferred-route";
27
29
 
28
30
  // =============================================================================
29
31
  // Per-template facts
@@ -189,6 +191,39 @@ export interface SourceSyncReviewReadyFacts {
189
191
  readonly checkpoint: "candidates" | "synthesis";
190
192
  }
191
193
 
194
+ // =============================================================================
195
+ // Workflow settlement facts (control ADR-CTRL-540 phase 2)
196
+ // =============================================================================
197
+
198
+ /**
199
+ * The settlement data part a pushed chat may carry: an explained, fact-free
200
+ * outcome.
201
+ */
202
+ export type ExplainedWorkflowOutcomeData = Omit<
203
+ WorkflowOutcomeData,
204
+ "outcome"
205
+ > & {
206
+ readonly outcome: ExplainedWorkflowOutcome;
207
+ };
208
+
209
+ /**
210
+ * A workflow ended in a failure a person can act on, and nobody was watching.
211
+ *
212
+ * FACT-FREE (INV-PROACTIVE-CONTENT). `narration` is the BACKEND's deterministic
213
+ * settlement copy (src/chat/workflow-outcome-copy.ts), rendered from the
214
+ * fact-stripped outcome (`facts: null`) — the same sentence the launching chat
215
+ * would have received, so one module owns settlement wording and it already
216
+ * carries the call-to-action ban. `outcome` is the settlement data part with
217
+ * the frozen outcome's facts stripped: reason and affordance ids only, each
218
+ * affordance re-gated per viewer at render and executed by the server.
219
+ */
220
+ export interface WorkflowNeedsAttentionFacts {
221
+ /** The backend settlement narration for this outcome, told with `facts: null`. */
222
+ readonly narration: string;
223
+ /** `{ workflowId, outcome }` — explained route only, `facts: null`. */
224
+ readonly outcome: ExplainedWorkflowOutcomeData;
225
+ }
226
+
192
227
  // =============================================================================
193
228
  // ProactiveChatFacts
194
229
  // =============================================================================
@@ -207,4 +242,5 @@ export interface ProactiveChatFacts {
207
242
  readonly billingSuspended: BillingSuspendedFacts;
208
243
  readonly billingSubscriptionCanceled: BillingSubscriptionCanceledFacts;
209
244
  readonly sourceSyncReviewReady: SourceSyncReviewReadyFacts;
245
+ readonly workflowNeedsAttention: WorkflowNeedsAttentionFacts;
210
246
  }
@@ -51,6 +51,8 @@ export type {
51
51
  BillingSuspendedFacts,
52
52
  BillingSubscriptionCanceledFacts,
53
53
  SourceSyncReviewReadyFacts,
54
+ ExplainedWorkflowOutcomeData,
55
+ WorkflowNeedsAttentionFacts,
54
56
  ProactiveChatFacts,
55
57
  } from "./facts";
56
58
  export { PROACTIVE_CHAT_MAX_REPLIES } from "./composer";
@@ -302,6 +302,35 @@ export const PROACTIVE_EVENT_KIND_IDS = [
302
302
  * times its job is redelivered.
303
303
  */
304
304
  "orgStructure.review_ready",
305
+
306
+ // ===========================================================================
307
+ // Workflow settlements (control ADR-CTRL-540 phase 2; control ADR
308
+ // affordance-system)
309
+ // ===========================================================================
310
+ //
311
+ // A workflow that settles with no launching conversation (a scheduled
312
+ // rediscovery, an upkeep re-synthesis, a launch nobody docked into a chat)
313
+ // is ROUTED here by the backend, per reason, through the frozen
314
+ // `*_DEFERRED_ROUTE_OF_REASON` tables in ../workflows/deferred-route.
315
+ //
316
+ // TRIGGER: the committed `deferred → routed` transition of the settlement
317
+ // row (INV-PROACTIVE-TRIGGER). OCCURRENCE KEY:
318
+ // `workflow-settled:{workflowId}:{executionEpoch}` — one settlement announces
319
+ // once, however often the sweep runs.
320
+ /**
321
+ * A workflow ended in a failure a person can act on. `explained` — a pushed
322
+ * chat that says what happened, FACT-FREE (reason only), carrying the frozen
323
+ * outcome so its grounded affordances render as controls; plus a born-read
324
+ * inbox row. No banner: one background run is not the workspace broken.
325
+ */
326
+ "workflow.needs_attention",
327
+ /**
328
+ * A workflow finished (or failed with nothing to offer) while nobody was
329
+ * watching. A `notice` — an unread inbox row and nothing else: telling it
330
+ * needs counts, which are authority-sensitive and must re-render under the
331
+ * reader's current authority, never freeze into a chat.
332
+ */
333
+ "workflow.finished",
305
334
  ] as const;
306
335
  export type ProactiveEventKind = (typeof PROACTIVE_EVENT_KIND_IDS)[number];
307
336
 
@@ -262,4 +262,26 @@ export const PROACTIVE_EVENT_KINDS = {
262
262
  class: "notice",
263
263
  inboxKind: "proactive.org_structure_review_ready",
264
264
  },
265
+
266
+ // ===========================================================================
267
+ // Workflow settlements (control ADR-CTRL-540 phase 2)
268
+ // ===========================================================================
269
+ //
270
+ // Routed per reason by the backend through ../workflows/deferred-route. NO
271
+ // PLAN SETS `emailKind` or `actionItemKind`: a background run's ending is
272
+ // announced, not standing work.
273
+ /**
274
+ * A failure a person can act on. EXPLAINED: the fact-free chat carries the
275
+ * badge and the frozen outcome; the inbox row is born read.
276
+ */
277
+ "workflow.needs_attention": {
278
+ class: "explained",
279
+ chatTemplate: "workflowNeedsAttention",
280
+ inboxKind: "proactive.workflow_needs_attention",
281
+ },
282
+ /** A finished run nobody watched. A NOTICE: the inbox row is the attention. */
283
+ "workflow.finished": {
284
+ class: "notice",
285
+ inboxKind: "proactive.workflow_finished",
286
+ },
265
287
  } as const satisfies Record<ProactiveEventKind, ProactiveEventDefinition>;
@@ -91,5 +91,7 @@ export const PROACTIVE_CHAT_TEMPLATES = [
91
91
  "billingSubscriptionCanceled",
92
92
  /** "A source review is ready — here is what it is and what happens if you leave it." */
93
93
  "sourceSyncReviewReady",
94
+ /** "A background run did not finish — here is what happened." Fact-free. */
95
+ "workflowNeedsAttention",
94
96
  ] as const;
95
97
  export type ProactiveChatTemplate = (typeof PROACTIVE_CHAT_TEMPLATES)[number];
@@ -66,6 +66,13 @@ chat. That is the precedent notify/'s README states for outbound notifications
66
66
  nothing. It names no count, no document and no source: all three are
67
67
  scope-gated, and a frozen sentence cannot be taken back.
68
68
  `../__tests__/vocabulary.test.ts` enforces "no digit" for both checkpoints.
69
+ - **`workflow-needs-attention.ts` FRAMES; IT DOES NOT NARRATE.** Its `text` is
70
+ the backend's settlement narration verbatim (one owner of settlement wording),
71
+ its title is per workflow type with no digit and no next step, it has no
72
+ chips, and it carries the fact-free outcome as `outcome` so the outcome's
73
+ grounded affordances render as real controls (control ADR
74
+ `affordance-system`). `../__tests__/workflow-attention.test.ts` pins all of
75
+ it.
69
76
 
70
77
  ## Public API
71
78
 
@@ -80,10 +87,11 @@ chat. That is the precedent notify/'s README states for outbound notifications
80
87
  | `billingSuspendedComposer` | The grace period elapsed; leads with "nothing was deleted" |
81
88
  | `billingSubscriptionCanceledComposer` | Seats ended; AI funds are not forfeit |
82
89
  | `sourceSyncReviewReadyComposer` | A source review waits; nothing has happened yet; no count |
90
+ | `workflowNeedsAttentionComposer` | A background run failed; frames the backend narration, no chips |
83
91
 
84
92
  ## Dependencies
85
93
 
86
94
  Type-only imports of `ProactiveChatComposer` (`../composer`),
87
95
  `ProactiveChatTemplate` (`../surfaces`) and the per-template facts
88
- (`../facts`). No `zod`, no runtime imports beyond the sibling template
96
+ (`../facts`), and of `WorkflowType` (`../../workflows`). No `zod`, no runtime imports beyond the sibling template
89
97
  modules.
@@ -31,6 +31,7 @@ import { billingSuspendedComposer } from "./billing-suspended";
31
31
  import { morningBriefComposer } from "./morning-brief";
32
32
  import { orgBecameSharedComposer } from "./org-became-shared";
33
33
  import { sourceSyncReviewReadyComposer } from "./source-sync-review-ready";
34
+ import { workflowNeedsAttentionComposer } from "./workflow-needs-attention";
34
35
 
35
36
  export const PROACTIVE_CHAT_COMPOSERS = {
36
37
  orgBecameShared: orgBecameSharedComposer,
@@ -41,4 +42,5 @@ export const PROACTIVE_CHAT_COMPOSERS = {
41
42
  billingSuspended: billingSuspendedComposer,
42
43
  billingSubscriptionCanceled: billingSubscriptionCanceledComposer,
43
44
  sourceSyncReviewReady: sourceSyncReviewReadyComposer,
45
+ workflowNeedsAttention: workflowNeedsAttentionComposer,
44
46
  } as const satisfies Record<ProactiveChatTemplate, ProactiveChatComposer>;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The workflow-needs-attention pushed chat — a background run ended in a
3
+ * failure a person can act on, while nobody was watching.
4
+ *
5
+ * THIS COMPOSER FRAMES; IT DOES NOT NARRATE. The sentence is the backend's
6
+ * deterministic settlement narration (one owner of settlement wording, already
7
+ * under the call-to-action ban), passed in as `narration`. The composer adds a
8
+ * title per workflow type and carries the fact-free outcome so its grounded
9
+ * affordances render as real controls (control ADR affordance-system). It
10
+ * offers no next step in words and no chips: a chip only sends a chat prompt,
11
+ * which is not grounded in a control.
12
+ *
13
+ * NO COUNTS (INV-PROACTIVE-CONTENT): the outcome's facts are stripped and the
14
+ * narration is told from the stripped outcome.
15
+ */
16
+ import type { WorkflowType } from "../../workflows/types";
17
+ import type { ProactiveChatComposer } from "../composer";
18
+
19
+ const TITLE_OF_WORKFLOW_TYPE = {
20
+ "source-sync/discover": "The drive search did not finish",
21
+ "source-sync/synthesize": "The document update did not finish",
22
+ "org-structure/hris-connect": "The org structure analysis did not finish",
23
+ "org-structure/reanalyze": "The org structure analysis did not finish",
24
+ } as const satisfies Record<WorkflowType, string>;
25
+
26
+ export const workflowNeedsAttentionComposer: ProactiveChatComposer<"workflowNeedsAttention"> =
27
+ {
28
+ template: "workflowNeedsAttention",
29
+ compose: (facts) => ({
30
+ title: TITLE_OF_WORKFLOW_TYPE[facts.outcome.outcome.workflowType],
31
+ text: facts.narration,
32
+ replies: [],
33
+ outcome: facts.outcome,
34
+ }),
35
+ };
@@ -48,6 +48,7 @@ documented in its owning domain (`src/org/`, `src/chat/`, `src/comments/`, …).
48
48
  **Internal domains:**
49
49
 
50
50
  - `action-items`
51
+ - `affordances`
51
52
  - `api`
52
53
  - `autotune`
53
54
  - `chat`
@@ -201,6 +201,8 @@ export type {
201
201
  ProactiveEventDefinition,
202
202
  OrgBecameSharedFacts,
203
203
  MorningBriefFacts,
204
+ ExplainedWorkflowOutcomeData,
205
+ WorkflowNeedsAttentionFacts,
204
206
  ProactiveChatFacts,
205
207
  ProactiveChatComposer,
206
208
  ProactiveChatMessage,
@@ -219,31 +221,60 @@ export type { WorkflowType } from "../workflows/index";
219
221
 
220
222
  // The typed outcome of a workflow's terminal transition (control ADR-CTRL-540):
221
223
  // per-type closed reasons, typed facts and an explicit disposition, classified
222
- // once by the backend and frozen. See src/workflows/outcome.ts.
224
+ // once by the backend and frozen. Each ending also freezes the next steps its
225
+ // reason offers (control ADR affordance-system); `affordancesOfOutcome` is the
226
+ // one projection consumers render from. An ending with no launching
227
+ // conversation takes its reason's deferred route (explained | notice | covered
228
+ // | silent). See src/workflows/outcome.ts and src/workflows/deferred-route.ts.
223
229
  export {
230
+ DEFERRED_ROUTES,
231
+ DISCOVER_AFFORDANCES_OF_REASON,
232
+ DISCOVER_DEFERRED_ROUTE_OF_REASON,
224
233
  DISCOVER_OUTCOME_KIND_OF_REASON,
234
+ DeferredRouteSchema,
225
235
  DiscoverOutcomeFactsSchema,
226
236
  DiscoverWorkflowOutcomeSchema,
227
237
  HrisConnectWorkflowOutcomeSchema,
238
+ ORG_STRUCTURE_AFFORDANCES_OF_REASON,
239
+ ORG_STRUCTURE_DEFERRED_ROUTE_OF_REASON,
228
240
  ORG_STRUCTURE_OUTCOME_KIND_OF_REASON,
229
241
  OrgStructureOutcomeFactsSchema,
230
242
  ReanalyzeWorkflowOutcomeSchema,
243
+ SETTLEMENT_AUDIENCE_POLICIES,
244
+ SETTLEMENT_COVERING_KINDS,
245
+ SETTLEMENT_SILENT_REASONS,
246
+ SYNTHESIZE_AFFORDANCES_OF_REASON,
247
+ SYNTHESIZE_DEFERRED_ROUTE_OF_REASON,
231
248
  SYNTHESIZE_OUTCOME_KIND_OF_REASON,
232
249
  SynthesizeOutcomeFactsSchema,
233
250
  SynthesizeWorkflowOutcomeSchema,
251
+ WORKFLOW_AFFORDANCES_OF_REASON,
252
+ WORKFLOW_DEFERRED_ROUTE_OF_REASON,
234
253
  WORKFLOW_OUTCOME_KINDS,
235
254
  WorkflowOutcomeDispositionSchema,
236
255
  WorkflowOutcomeSchema,
256
+ affordancesOfOutcome,
257
+ affordancesOfReason,
258
+ deferredRouteOf,
259
+ toExplainedOutcome,
237
260
  } from "../workflows/index";
238
261
 
239
262
  export type {
263
+ DeferredRoute,
264
+ DeferredRouteKind,
240
265
  DiscoverOutcomeFacts,
241
266
  DiscoverOutcomeReason,
242
267
  DiscoverWorkflowOutcome,
268
+ ExplainedDiscoverReason,
269
+ ExplainedOrgStructureReason,
270
+ ExplainedSynthesizeReason,
271
+ ExplainedWorkflowOutcome,
243
272
  HrisConnectWorkflowOutcome,
244
273
  OrgStructureOutcomeFacts,
245
274
  OrgStructureOutcomeReason,
246
275
  ReanalyzeWorkflowOutcome,
276
+ SettlementAudiencePolicy,
277
+ SettlementCoveringKind,
247
278
  SynthesizeOutcomeFacts,
248
279
  SynthesizeOutcomeReason,
249
280
  SynthesizeWorkflowOutcome,
@@ -252,3 +283,40 @@ export type {
252
283
  WorkflowOutcomeKind,
253
284
  WorkflowOutcomeOf,
254
285
  } from "../workflows/index";
286
+
287
+ // Affordances (control ADR affordance-system): the closed vocabulary of next
288
+ // steps the product may offer, as INSTANCES ({ id, args? }), with each one's
289
+ // binding kind and live availability condition. An affordance is offered only
290
+ // by a domain-owned deterministic rule; existing here never offers it. See
291
+ // src/affordances/README.md.
292
+ export {
293
+ AFFORDANCE_AVAILABILITY_IDS,
294
+ AFFORDANCE_AVAILABILITY_OF,
295
+ AFFORDANCE_ARGS,
296
+ AFFORDANCE_BINDING_KINDS,
297
+ AFFORDANCE_BINDING_KIND_OF,
298
+ AFFORDANCE_IDS,
299
+ AFFORDANCE_UNAVAILABLE_REASONS,
300
+ AffordanceAvailabilityIdSchema,
301
+ AffordanceAvailabilitySchema,
302
+ AffordanceIdSchema,
303
+ AffordanceSchema,
304
+ MAX_OFFERED_AFFORDANCES,
305
+ NO_ARG_AFFORDANCE_IDS,
306
+ OfferedAffordanceSchema,
307
+ PROJECTED_AFFORDANCE_IDS,
308
+ ReviewOpenArgsSchema,
309
+ sameAffordances,
310
+ } from "../affordances/index";
311
+
312
+ export type {
313
+ Affordance,
314
+ AffordanceArgs,
315
+ AffordanceAvailability,
316
+ AffordanceAvailabilityId,
317
+ AffordanceBindingKind,
318
+ AffordanceId,
319
+ AffordanceUnavailableReason,
320
+ OfferedAffordance,
321
+ ProjectedAffordanceId,
322
+ } from "../affordances/index";
@@ -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",