@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.
- package/package.json +4 -3
- package/src/affordances/README.md +65 -0
- package/src/affordances/__tests__/README.md +30 -0
- package/src/affordances/__tests__/affordances.test.ts +251 -0
- package/src/affordances/affordance.ts +124 -0
- package/src/affordances/args.ts +59 -0
- package/src/affordances/availability.ts +78 -0
- package/src/affordances/ids.ts +45 -0
- package/src/affordances/index.ts +39 -0
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +142 -2
- package/src/generated/openapi-routes.ts +1 -0
- package/src/proactive/README.md +26 -4
- package/src/proactive/__tests__/README.md +10 -0
- package/src/proactive/__tests__/chat-templates.test.ts +8 -0
- package/src/proactive/__tests__/vocabulary.test.ts +11 -0
- package/src/proactive/__tests__/workflow-attention-fixtures.ts +49 -0
- package/src/proactive/__tests__/workflow-attention.test.ts +146 -0
- package/src/proactive/composer.ts +8 -0
- package/src/proactive/facts.ts +36 -0
- package/src/proactive/index.ts +2 -0
- package/src/proactive/kinds.ts +29 -0
- package/src/proactive/registry.ts +22 -0
- package/src/proactive/surfaces.ts +2 -0
- package/src/proactive/templates/README.md +9 -1
- package/src/proactive/templates/index.ts +2 -0
- package/src/proactive/templates/workflow-needs-attention.ts +35 -0
- package/src/root-exports/README.md +1 -0
- package/src/root-exports/delivery.ts +69 -1
- package/src/source-sync/README.md +95 -5
- package/src/source-sync/__tests__/attention-evidence.test.ts +96 -0
- package/src/source-sync/__tests__/barrel.test.ts +39 -0
- package/src/source-sync/__tests__/enumeration.test.ts +160 -0
- package/src/source-sync/__tests__/schemas.test.ts +223 -0
- package/src/source-sync/__tests__/vocabulary.test.ts +104 -5
- package/src/source-sync/attention-evidence.ts +93 -0
- package/src/source-sync/candidate-item.ts +25 -0
- package/src/source-sync/enumeration.ts +152 -0
- package/src/source-sync/index.ts +35 -1
- package/src/source-sync/relevance.ts +85 -11
- package/src/source-sync/vocabulary.ts +37 -1
- package/src/user-notifications/README.md +8 -0
- package/src/user-notifications/kinds.ts +13 -0
- package/src/user-notifications/lifecycle-registry.ts +10 -0
- package/src/workflows/README.md +38 -2
- package/src/workflows/__tests__/README.md +21 -2
- package/src/workflows/__tests__/affordances-of-outcome.test.ts +99 -0
- package/src/workflows/__tests__/deferred-route.test.ts +168 -0
- package/src/workflows/__tests__/outcome.test.ts +66 -0
- package/src/workflows/affordances-of-outcome.ts +65 -0
- package/src/workflows/deferred-route.ts +240 -0
- package/src/workflows/index.ts +34 -0
- package/src/workflows/outcome.ts +114 -1
|
@@ -18,6 +18,7 @@ import { IsoDateTime } from "../api/primitives";
|
|
|
18
18
|
import {
|
|
19
19
|
AUDIENCE_FITS,
|
|
20
20
|
DOCUMENT_TYPES,
|
|
21
|
+
OPERATING_KNOWLEDGE_FAMILIES,
|
|
21
22
|
SHARING_BREADTHS,
|
|
22
23
|
SOURCE_CLASSES,
|
|
23
24
|
SOURCE_SENSITIVITY_CLASSES,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
SOURCE_RELEVANCE_TIERS,
|
|
35
36
|
TARGET_AUDIENCES,
|
|
36
37
|
} from "./relevance";
|
|
38
|
+
import { AttentionEvidenceSchema } from "./attention-evidence";
|
|
37
39
|
|
|
38
40
|
/** ONE source proposed for ONE target document. */
|
|
39
41
|
export const SourceSyncCandidateItemSchema = z
|
|
@@ -75,6 +77,22 @@ export const SourceSyncCandidateItemSchema = z
|
|
|
75
77
|
documentType: z.enum(DOCUMENT_TYPES).nullable(),
|
|
76
78
|
/** Confidence of the resolved type, 0..1. NULL exactly when documentType is null or `unknown`. */
|
|
77
79
|
documentTypeConfidence: z.number().min(0).max(1).nullable(),
|
|
80
|
+
/**
|
|
81
|
+
* WHAT KIND of operating knowledge the line is — a NAME the backend RESOLVES from the
|
|
82
|
+
* resolved document type through its versioned policy table (control ADR
|
|
83
|
+
* index-first-operating-knowledge-discovery), exactly as it resolves sourceClass. The
|
|
84
|
+
* backend's internal relevance-policy families never travel. NULL when the type is
|
|
85
|
+
* null or unknown, or names no operating knowledge (a legal agreement, a transaction,
|
|
86
|
+
* personal material), and on every line of a run that predates index-first discovery.
|
|
87
|
+
*/
|
|
88
|
+
operatingKnowledgeFamily: z.enum(OPERATING_KNOWLEDGE_FAMILIES).nullable(),
|
|
89
|
+
/**
|
|
90
|
+
* WHERE the source lives in the provider ("Product/Planning/2026") — the Tier-1
|
|
91
|
+
* canonical path the backend resolves from its location graph once the file reached
|
|
92
|
+
* T1 attention (Tier-0 never retains a name or a path). NULL for a file that never
|
|
93
|
+
* reached attention, on a withheld line, and on a run that predates index-first discovery.
|
|
94
|
+
*/
|
|
95
|
+
canonicalPath: z.string().min(1).max(1024).nullable(),
|
|
78
96
|
periodLabel: z.string().max(64).nullable(),
|
|
79
97
|
seriesRole: z.enum(["current", "history"]).nullable(),
|
|
80
98
|
/** The FUSED relevance score (control ADR source-discovery-relevance-funnel), 0..1. */
|
|
@@ -106,6 +124,13 @@ export const SourceSyncCandidateItemSchema = z
|
|
|
106
124
|
targetAudience: z.enum(TARGET_AUDIENCES).nullable(),
|
|
107
125
|
/** Which lane earned each model tier's attention (INV-SOURCESYNC-EXPLORATION-RESERVED). */
|
|
108
126
|
attention: CandidateAttentionSchema,
|
|
127
|
+
/**
|
|
128
|
+
* WHY the line received T1 attention: each signal with its producer and version
|
|
129
|
+
* (INV-SOURCESYNC-ATTENTION-EVIDENCE-HAS-PROVENANCE). NULL for a file that never
|
|
130
|
+
* reached attention, on a withheld line (terms and folder ids identify a source), and
|
|
131
|
+
* on every line of a run that predates index-first discovery.
|
|
132
|
+
*/
|
|
133
|
+
attentionEvidence: AttentionEvidenceSchema.nullable(),
|
|
109
134
|
/**
|
|
110
135
|
* Whether this line is in the set that approval would land — the SERVER's
|
|
111
136
|
* answer, never derived from `use`. A decoy, a series' history or a line a
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index-first discovery's ENUMERATION vocabulary (control ADR
|
|
3
|
+
* index-first-operating-knowledge-discovery; INV-SOURCESYNC-PARTIAL-ENUMERATION-IS-UNKNOWN).
|
|
4
|
+
*
|
|
5
|
+
* A discovery run first enumerates every source reachable through the connection,
|
|
6
|
+
* root by root (the person's own corpus, each shared drive, the domain corpus).
|
|
7
|
+
* How completely it did so is a closed answer that separates OUR ceiling from the
|
|
8
|
+
* PROVIDER's incompleteness, so a partial enumeration can never read as an index of
|
|
9
|
+
* the corpus: only `complete` lets a surface say "full index". Anything else means
|
|
10
|
+
* absence is UNKNOWN, never negative evidence.
|
|
11
|
+
*
|
|
12
|
+
* Stage counters name what each count did: enumerated / metadata-eligible / T0-scored
|
|
13
|
+
* are metadata only; attended / peeked / classified involve a model; shown / retained
|
|
14
|
+
* are outcomes. `filesEnumerated = 100000` with `local_budget_truncated` never reads as
|
|
15
|
+
* "indexed every file".
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { IsoDateTime } from "../api/primitives";
|
|
19
|
+
|
|
20
|
+
/** How completely a run (or one root) was enumerated. */
|
|
21
|
+
export const ENUMERATION_COMPLETENESS = [
|
|
22
|
+
"complete",
|
|
23
|
+
"local_budget_truncated",
|
|
24
|
+
"provider_incomplete",
|
|
25
|
+
"both",
|
|
26
|
+
] as const;
|
|
27
|
+
export type EnumerationCompleteness = (typeof ENUMERATION_COMPLETENESS)[number];
|
|
28
|
+
|
|
29
|
+
/** Which corpus a root is: the connected person's, one shared drive, or the domain. */
|
|
30
|
+
export const ENUMERATION_ROOT_KINDS = [
|
|
31
|
+
"user",
|
|
32
|
+
"shared_drive",
|
|
33
|
+
"domain",
|
|
34
|
+
] as const;
|
|
35
|
+
export type EnumerationRootKind = (typeof ENUMERATION_ROOT_KINDS)[number];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* PURE: one completeness for many roots. Our ceiling anywhere and the provider's
|
|
39
|
+
* incompleteness anywhere combine to `both`; only every root complete is `complete`.
|
|
40
|
+
*/
|
|
41
|
+
export function combineEnumerationCompleteness(
|
|
42
|
+
statuses: readonly EnumerationCompleteness[],
|
|
43
|
+
): EnumerationCompleteness {
|
|
44
|
+
const local = statuses.some(
|
|
45
|
+
(s) => s === "local_budget_truncated" || s === "both",
|
|
46
|
+
);
|
|
47
|
+
const provider = statuses.some(
|
|
48
|
+
(s) => s === "provider_incomplete" || s === "both",
|
|
49
|
+
);
|
|
50
|
+
if (local && provider) return "both";
|
|
51
|
+
if (local) return "local_budget_truncated";
|
|
52
|
+
if (provider) return "provider_incomplete";
|
|
53
|
+
return "complete";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const MS = (iso: string): number => Date.parse(iso);
|
|
57
|
+
|
|
58
|
+
/** A modified-time range the sampler did not enumerate. */
|
|
59
|
+
export const EnumerationSkippedRangeSchema = z
|
|
60
|
+
.object({ from: IsoDateTime, to: IsoDateTime })
|
|
61
|
+
.refine((r) => MS(r.from) <= MS(r.to), {
|
|
62
|
+
message: "a skipped range ends no earlier than it starts",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** How far one root's enumeration got. Counts, times and provider ids only — never a name. */
|
|
66
|
+
export const RootCoverageSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
rootKind: z.enum(ENUMERATION_ROOT_KINDS),
|
|
69
|
+
/** The shared drive's provider id; null for the user and domain corpora. */
|
|
70
|
+
rootId: z.string().min(1).max(128).nullable(),
|
|
71
|
+
status: z.enum(ENUMERATION_COMPLETENESS),
|
|
72
|
+
enumeratedCount: z.number().int().min(0),
|
|
73
|
+
/** Null exactly when nothing was enumerated under the root. */
|
|
74
|
+
observedOldestModifiedTime: IsoDateTime.nullable(),
|
|
75
|
+
observedNewestModifiedTime: IsoDateTime.nullable(),
|
|
76
|
+
skippedRanges: z.array(EnumerationSkippedRangeSchema).max(16),
|
|
77
|
+
})
|
|
78
|
+
.superRefine((root, ctx) => {
|
|
79
|
+
if ((root.rootId !== null) !== (root.rootKind === "shared_drive")) {
|
|
80
|
+
ctx.addIssue({
|
|
81
|
+
code: "custom",
|
|
82
|
+
message: "a root id names exactly a shared drive",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (root.status === "complete" && root.skippedRanges.length > 0) {
|
|
86
|
+
ctx.addIssue({
|
|
87
|
+
code: "custom",
|
|
88
|
+
message: "a complete root skips nothing",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const empty = root.enumeratedCount === 0;
|
|
92
|
+
const timesAbsent =
|
|
93
|
+
root.observedOldestModifiedTime === null &&
|
|
94
|
+
root.observedNewestModifiedTime === null;
|
|
95
|
+
const timesPresent =
|
|
96
|
+
root.observedOldestModifiedTime !== null &&
|
|
97
|
+
root.observedNewestModifiedTime !== null;
|
|
98
|
+
if (empty ? !timesAbsent : !timesPresent) {
|
|
99
|
+
ctx.addIssue({
|
|
100
|
+
code: "custom",
|
|
101
|
+
message:
|
|
102
|
+
"observed modified times are present exactly when the root enumerated a file",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
timesPresent &&
|
|
107
|
+
MS(root.observedOldestModifiedTime as string) >
|
|
108
|
+
MS(root.observedNewestModifiedTime as string)
|
|
109
|
+
) {
|
|
110
|
+
ctx.addIssue({
|
|
111
|
+
code: "custom",
|
|
112
|
+
message: "the oldest observed time is not after the newest",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
export type RootCoverage = z.infer<typeof RootCoverageSchema>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A run's stage counters. Each narrows the one before it:
|
|
120
|
+
* enumerated >= metadata-eligible >= T0-scored >= T1-attended >= peeked;
|
|
121
|
+
* T0-scored >= classified; T0-scored >= retained >= shown.
|
|
122
|
+
*/
|
|
123
|
+
export const DiscoveryStageCountersSchema = z
|
|
124
|
+
.object({
|
|
125
|
+
filesEnumerated: z.number().int().min(0),
|
|
126
|
+
filesMetadataEligible: z.number().int().min(0),
|
|
127
|
+
filesT0Scored: z.number().int().min(0),
|
|
128
|
+
filesT1Attended: z.number().int().min(0),
|
|
129
|
+
filesPeeked: z.number().int().min(0),
|
|
130
|
+
filesClassified: z.number().int().min(0),
|
|
131
|
+
filesShown: z.number().int().min(0),
|
|
132
|
+
filesRetained: z.number().int().min(0),
|
|
133
|
+
})
|
|
134
|
+
.superRefine((c, ctx) => {
|
|
135
|
+
const narrows =
|
|
136
|
+
c.filesMetadataEligible <= c.filesEnumerated &&
|
|
137
|
+
c.filesT0Scored <= c.filesMetadataEligible &&
|
|
138
|
+
c.filesT1Attended <= c.filesT0Scored &&
|
|
139
|
+
c.filesPeeked <= c.filesT1Attended &&
|
|
140
|
+
c.filesClassified <= c.filesT0Scored &&
|
|
141
|
+
c.filesRetained <= c.filesT0Scored &&
|
|
142
|
+
c.filesShown <= c.filesRetained;
|
|
143
|
+
if (!narrows) {
|
|
144
|
+
ctx.addIssue({
|
|
145
|
+
code: "custom",
|
|
146
|
+
message: "stage counters narrow from enumerated to shown",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
export type DiscoveryStageCounters = z.infer<
|
|
151
|
+
typeof DiscoveryStageCountersSchema
|
|
152
|
+
>;
|
package/src/source-sync/index.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the closed Source Sync vocabularies (the sets the backend persists
|
|
5
5
|
* and mirrors as database CHECK constraints, and the app handles exhaustively)
|
|
6
|
-
* including the discovery relevance vocabulary, the
|
|
6
|
+
* including the discovery relevance vocabulary, the index-first enumeration
|
|
7
|
+
* vocabulary (completeness, root coverage, stage counters), the attention
|
|
8
|
+
* evidence schemas, the discovery and candidate-set response schemas, the candidate-edit and
|
|
7
9
|
* per-scope approval request and response schemas, the per-document change
|
|
8
10
|
* plan and apply schemas, the source-resolution rule schemas, the staleness
|
|
9
11
|
* finding and standing-order schemas, and the Drive-reach status and upgrade
|
|
@@ -31,6 +33,7 @@ export type {
|
|
|
31
33
|
SourceSyncScopeApproverKind,
|
|
32
34
|
DocumentType,
|
|
33
35
|
KnownDocumentType,
|
|
36
|
+
OperatingKnowledgeFamily,
|
|
34
37
|
} from "./vocabulary";
|
|
35
38
|
|
|
36
39
|
export {
|
|
@@ -45,6 +48,7 @@ export {
|
|
|
45
48
|
SOURCE_SYNC_SCOPE_APPROVAL_STATES,
|
|
46
49
|
SOURCE_SYNC_SCOPE_APPROVER_KINDS,
|
|
47
50
|
DOCUMENT_TYPES,
|
|
51
|
+
OPERATING_KNOWLEDGE_FAMILIES,
|
|
48
52
|
} from "./vocabulary";
|
|
49
53
|
|
|
50
54
|
export type {
|
|
@@ -86,6 +90,36 @@ export {
|
|
|
86
90
|
refineCandidateRelevance,
|
|
87
91
|
} from "./relevance";
|
|
88
92
|
|
|
93
|
+
export type {
|
|
94
|
+
EnumerationCompleteness,
|
|
95
|
+
EnumerationRootKind,
|
|
96
|
+
RootCoverage,
|
|
97
|
+
DiscoveryStageCounters,
|
|
98
|
+
} from "./enumeration";
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
ENUMERATION_COMPLETENESS,
|
|
102
|
+
ENUMERATION_ROOT_KINDS,
|
|
103
|
+
combineEnumerationCompleteness,
|
|
104
|
+
EnumerationSkippedRangeSchema,
|
|
105
|
+
RootCoverageSchema,
|
|
106
|
+
DiscoveryStageCountersSchema,
|
|
107
|
+
} from "./enumeration";
|
|
108
|
+
|
|
109
|
+
export type {
|
|
110
|
+
AttentionSignalKind,
|
|
111
|
+
AttentionSignalSource,
|
|
112
|
+
AttentionSignal,
|
|
113
|
+
AttentionEvidence,
|
|
114
|
+
} from "./attention-evidence";
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
ATTENTION_SIGNAL_KINDS,
|
|
118
|
+
AttentionSignalSourceSchema,
|
|
119
|
+
AttentionSignalSchema,
|
|
120
|
+
AttentionEvidenceSchema,
|
|
121
|
+
} from "./attention-evidence";
|
|
122
|
+
|
|
89
123
|
export type {
|
|
90
124
|
SourceSyncCandidateItem,
|
|
91
125
|
SourceSyncCandidateItemPatch,
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
import {
|
|
11
|
+
combineEnumerationCompleteness,
|
|
12
|
+
DiscoveryStageCountersSchema,
|
|
13
|
+
ENUMERATION_COMPLETENESS,
|
|
14
|
+
RootCoverageSchema,
|
|
15
|
+
} from "./enumeration";
|
|
10
16
|
|
|
11
17
|
/** The funnel's verdict. `dropped` is persisted (drop ledger / re-scored observation) and never reaches the wire item. */
|
|
12
18
|
export const SOURCE_RELEVANCE_TIERS = ["dropped", "hidden", "shown"] as const;
|
|
@@ -165,17 +171,50 @@ export type CandidateAttention = z.infer<typeof CandidateAttentionSchema>;
|
|
|
165
171
|
|
|
166
172
|
/**
|
|
167
173
|
* The last completed discovery run's counts, so no surface shows one ambiguous total.
|
|
168
|
-
* The set response carries it NULL before any run has completed.
|
|
174
|
+
* The set response carries it NULL before any run has completed. The three enumeration
|
|
175
|
+
* fields are all present or all absent: NULL on all three = the run predates index-first
|
|
176
|
+
* discovery (control ADR index-first-operating-knowledge-discovery).
|
|
169
177
|
*/
|
|
170
|
-
export const CandidateDiscoverySummarySchema = z
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
export const CandidateDiscoverySummarySchema = z
|
|
179
|
+
.object({
|
|
180
|
+
foundThisRun: z.number().int().min(0),
|
|
181
|
+
retainedDecisions: z.number().int().min(0),
|
|
182
|
+
notRediscovered: z.number().int().min(0),
|
|
183
|
+
pendingClassification: z.number().int().min(0),
|
|
184
|
+
filesDropped: z.number().int().min(0),
|
|
185
|
+
coveragePercent: z.number().int().min(0).max(100),
|
|
186
|
+
scanOutcome: z.enum(SCAN_OUTCOMES),
|
|
187
|
+
/** How completely the run enumerated; only `complete` may be called a full index. */
|
|
188
|
+
enumerationCompleteness: z.enum(ENUMERATION_COMPLETENESS).nullable(),
|
|
189
|
+
/** One entry per enumerated root. */
|
|
190
|
+
rootCoverage: z.array(RootCoverageSchema).min(1).max(64).nullable(),
|
|
191
|
+
/** What each stage did, by count (filesEnumerated is metadata only — never "indexed"). */
|
|
192
|
+
stageCounters: DiscoveryStageCountersSchema.nullable(),
|
|
193
|
+
})
|
|
194
|
+
.superRefine((s, ctx) => {
|
|
195
|
+
const present = [
|
|
196
|
+
s.enumerationCompleteness,
|
|
197
|
+
s.rootCoverage,
|
|
198
|
+
s.stageCounters,
|
|
199
|
+
].filter((v) => v !== null).length;
|
|
200
|
+
if (present !== 0 && present !== 3) {
|
|
201
|
+
ctx.addIssue({
|
|
202
|
+
code: "custom",
|
|
203
|
+
message: "enumeration fields are all present or all absent",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
s.rootCoverage !== null &&
|
|
208
|
+
s.enumerationCompleteness !== null &&
|
|
209
|
+
combineEnumerationCompleteness(s.rootCoverage.map((r) => r.status)) !==
|
|
210
|
+
s.enumerationCompleteness
|
|
211
|
+
) {
|
|
212
|
+
ctx.addIssue({
|
|
213
|
+
code: "custom",
|
|
214
|
+
message: "enumeration completeness is the combination of its roots",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
179
218
|
export type CandidateDiscoverySummary = z.infer<
|
|
180
219
|
typeof CandidateDiscoverySummarySchema
|
|
181
220
|
>;
|
|
@@ -191,6 +230,11 @@ type CandidateRelevanceFacts = {
|
|
|
191
230
|
selectionSource: SelectionSource | null;
|
|
192
231
|
documentType: string | null;
|
|
193
232
|
documentTypeConfidence: number | null;
|
|
233
|
+
withheld: boolean;
|
|
234
|
+
attention: { t1: AttentionLane | null; t2: AttentionLane | null };
|
|
235
|
+
operatingKnowledgeFamily: string | null;
|
|
236
|
+
canonicalPath: string | null;
|
|
237
|
+
attentionEvidence: readonly unknown[] | null;
|
|
194
238
|
};
|
|
195
239
|
|
|
196
240
|
/**
|
|
@@ -198,7 +242,10 @@ type CandidateRelevanceFacts = {
|
|
|
198
242
|
* a hidden line is never included; classification decides whether a recommendation and a
|
|
199
243
|
* class exist; `selectionSource` is present exactly when a treatment is committed. A
|
|
200
244
|
* document type exists only on a classified line, and carries a confidence exactly when
|
|
201
|
-
* it is a known type (control ADR source-discovery-observation-vs-policy).
|
|
245
|
+
* it is a known type (control ADR source-discovery-observation-vs-policy). An
|
|
246
|
+
* operating-knowledge family needs a known type; attention evidence and a canonical path
|
|
247
|
+
* exist only on a line T1 attended and never on a withheld one (control ADR
|
|
248
|
+
* index-first-operating-knowledge-discovery).
|
|
202
249
|
*/
|
|
203
250
|
export function refineCandidateRelevance(
|
|
204
251
|
item: CandidateRelevanceFacts,
|
|
@@ -248,4 +295,31 @@ export function refineCandidateRelevance(
|
|
|
248
295
|
"a known document type carries a confidence; unknown and absent carry none",
|
|
249
296
|
});
|
|
250
297
|
}
|
|
298
|
+
if (item.operatingKnowledgeFamily !== null && !knownType) {
|
|
299
|
+
ctx.addIssue({
|
|
300
|
+
code: "custom",
|
|
301
|
+
message: "an operating-knowledge family needs a known document type",
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (
|
|
305
|
+
item.withheld &&
|
|
306
|
+
(item.attentionEvidence !== null || item.canonicalPath !== null)
|
|
307
|
+
) {
|
|
308
|
+
ctx.addIssue({
|
|
309
|
+
code: "custom",
|
|
310
|
+
message: "a withheld line carries no attention evidence and no path",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (item.attentionEvidence !== null && item.attention.t1 === null) {
|
|
314
|
+
ctx.addIssue({
|
|
315
|
+
code: "custom",
|
|
316
|
+
message: "attention evidence exists only on a line T1 attended",
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (item.canonicalPath !== null && item.attention.t1 === null) {
|
|
320
|
+
ctx.addIssue({
|
|
321
|
+
code: "custom",
|
|
322
|
+
message: "a canonical path exists only on a line T1 attended",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
251
325
|
}
|
|
@@ -113,7 +113,9 @@ export type SourceSyncScopeApproverKind =
|
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* What a source IS — a STRUCTURAL name the model observes, never a relevance verdict
|
|
116
|
-
* (control ADR source-discovery-observation-vs-policy).
|
|
116
|
+
* (control ADR source-discovery-observation-vs-policy). The set names operating
|
|
117
|
+
* knowledge — charters, playbooks, policies, decision logs, postmortems and the like —
|
|
118
|
+
* beside strategy (control ADR index-first-operating-knowledge-discovery). Product policy in the backend maps a
|
|
117
119
|
* resolved type to a family, a source class, a sensitivity and a relevance policy; none of
|
|
118
120
|
* that travels on the wire. A medium is not a meaning: `image_or_screenshot` is graded on
|
|
119
121
|
* content. `unknown` is a real answer (the model could not tell, or two reads disagreed).
|
|
@@ -151,7 +153,41 @@ export const DOCUMENT_TYPES = [
|
|
|
151
153
|
"coursework",
|
|
152
154
|
"empty_template",
|
|
153
155
|
"image_or_screenshot",
|
|
156
|
+
// Operating knowledge (control ADR index-first-operating-knowledge-discovery):
|
|
157
|
+
"team_charter",
|
|
158
|
+
"process_playbook",
|
|
159
|
+
"policy_handbook",
|
|
160
|
+
"onboarding_guide",
|
|
161
|
+
"values_culture",
|
|
162
|
+
"positioning_messaging",
|
|
163
|
+
"decision_log",
|
|
164
|
+
"postmortem",
|
|
165
|
+
"metrics_definition",
|
|
166
|
+
"operating_cadence",
|
|
154
167
|
"unknown",
|
|
155
168
|
] as const;
|
|
156
169
|
export type DocumentType = (typeof DOCUMENT_TYPES)[number];
|
|
157
170
|
export type KnownDocumentType = Exclude<DocumentType, "unknown">;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* WHAT KIND of operating knowledge a source is: what the company is trying to do
|
|
174
|
+
* (direction), how it is organized to do it (operating_model), how it runs
|
|
175
|
+
* (practices), who it is and for whom (identity_market), and what it decided and
|
|
176
|
+
* learned (decisions_learning). Control ADR index-first-operating-knowledge-discovery.
|
|
177
|
+
*
|
|
178
|
+
* A NAME the product derives from the resolved document type — the backend's
|
|
179
|
+
* versioned policy table owns the type -> family mapping, exactly as it owns
|
|
180
|
+
* sourceClass. It is NOT the backend's relevance-policy family (strategy,
|
|
181
|
+
* reference, corporate_record, …), which never travels on the wire. Board and
|
|
182
|
+
* financial material are ordinary members of `direction`; no family is required
|
|
183
|
+
* to exist for a discovery to be valid.
|
|
184
|
+
*/
|
|
185
|
+
export const OPERATING_KNOWLEDGE_FAMILIES = [
|
|
186
|
+
"direction",
|
|
187
|
+
"operating_model",
|
|
188
|
+
"practices",
|
|
189
|
+
"identity_market",
|
|
190
|
+
"decisions_learning",
|
|
191
|
+
] as const;
|
|
192
|
+
export type OperatingKnowledgeFamily =
|
|
193
|
+
(typeof OPERATING_KNOWLEDGE_FAMILIES)[number];
|
|
@@ -78,6 +78,14 @@ that renders them alongside action items at the top of `/me/work`.
|
|
|
78
78
|
- **`proactive.org_structure_review_ready` is the org structure notice's row.**
|
|
79
79
|
It arrives UNREAD, because a `notice` has no other surface to hold the badge.
|
|
80
80
|
The renderer links it to the place the review is opened.
|
|
81
|
+
- **`proactive.workflow_needs_attention` and `proactive.workflow_finished` are
|
|
82
|
+
the workflow settlement rows** (control ADR-CTRL-540 phase 2), the durable
|
|
83
|
+
twins of `workflow.needs_attention` (explained, so born READ) and
|
|
84
|
+
`workflow.finished` (a notice, so UNREAD). A row holds identifiers only — the
|
|
85
|
+
workflow id and type; a finished run's counts render at read time under the
|
|
86
|
+
reader's authority. Both supersede per workflow type (condition field
|
|
87
|
+
`workflowType`, renewing): one active row per workflow type per reader, and a
|
|
88
|
+
newer settlement is news again.
|
|
81
89
|
- **This is a mention row, not a chat-unread model.** Being named is a discrete
|
|
82
90
|
fact you come back to; "this chat has new messages" is not modeled here or
|
|
83
91
|
anywhere, and the proactive `unread` flag on a chat summary stays a one-time
|
|
@@ -229,6 +229,19 @@ export const USER_NOTIFICATION_KINDS = [
|
|
|
229
229
|
* where the review is opened.
|
|
230
230
|
*/
|
|
231
231
|
"proactive.org_structure_review_ready",
|
|
232
|
+
|
|
233
|
+
// ===========================================================================
|
|
234
|
+
// Workflow settlements (control ADR-CTRL-540 phase 2)
|
|
235
|
+
// ===========================================================================
|
|
236
|
+
//
|
|
237
|
+
// The durable twins of `workflow.needs_attention` / `workflow.finished`,
|
|
238
|
+
// rendered by `src/proactive/` in the backend, DISTINCT from the kind strings.
|
|
239
|
+
// A row holds identifiers only (the workflow id and type); a `notice` row's
|
|
240
|
+
// counts render at read time under the reader's authority.
|
|
241
|
+
/** A failure a person can act on. Explained: the chat holds the badge, born read. */
|
|
242
|
+
"proactive.workflow_needs_attention",
|
|
243
|
+
/** A finished run nobody watched. A notice: this row IS the attention, unread. */
|
|
244
|
+
"proactive.workflow_finished",
|
|
232
245
|
] as const;
|
|
233
246
|
|
|
234
247
|
export type UserNotificationKind = (typeof USER_NOTIFICATION_KINDS)[number];
|
|
@@ -32,6 +32,14 @@ const STANDING = {
|
|
|
32
32
|
resolution: RESOLVED_FOR_ALL,
|
|
33
33
|
retention: "standard",
|
|
34
34
|
} as const;
|
|
35
|
+
/** One active row per workflow type per reader; a newer settlement renews it. */
|
|
36
|
+
const PER_WORKFLOW_TYPE = {
|
|
37
|
+
condition: { scope: "fields", fields: ["workflowType"] },
|
|
38
|
+
supersedes: true,
|
|
39
|
+
attentionOnSupersede: "renew",
|
|
40
|
+
resolution: "never",
|
|
41
|
+
retention: "standard",
|
|
42
|
+
} as const;
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
45
|
* Every kind's lifecycle. Annotated (not `satisfies`) so a missing kind and an
|
|
@@ -120,4 +128,6 @@ export const NOTIFICATION_LIFECYCLE: Readonly<Record<UserNotificationKind, Notif
|
|
|
120
128
|
resolution: RESOLVED_FOR_ALL,
|
|
121
129
|
retention: "standard",
|
|
122
130
|
},
|
|
131
|
+
"proactive.workflow_needs_attention": PER_WORKFLOW_TYPE,
|
|
132
|
+
"proactive.workflow_finished": PER_WORKFLOW_TYPE,
|
|
123
133
|
};
|
package/src/workflows/README.md
CHANGED
|
@@ -40,6 +40,30 @@ so every producer, validator and renderer agrees on it.
|
|
|
40
40
|
adding its outcome member; the union must cover every registered type.
|
|
41
41
|
- **`unknown` is a safety valve, not a status.** It names an unrecognized
|
|
42
42
|
terminal transition, never a workflow whose state could not be read.
|
|
43
|
+
- **The offer is a function of reason** (control ADR affordance-system). Every
|
|
44
|
+
outcome carries a REQUIRED `affordances` list — the ordered next steps the
|
|
45
|
+
ending offers — fixed by its reason's per-type `*_AFFORDANCES_OF_REASON`
|
|
46
|
+
table and frozen with the outcome. The tables are total (an empty list is an
|
|
47
|
+
explicit decision) and the schema refuses any other list, a missing key and
|
|
48
|
+
more than `MAX_OFFERED_AFFORDANCES`. Producers read the offer through
|
|
49
|
+
`affordancesOfReason` only.
|
|
50
|
+
- **`review.open` is projected, never stored.** It is derived from a `review`
|
|
51
|
+
disposition; an offer that contains it is refused. `affordancesOfOutcome` is
|
|
52
|
+
the single projection every consumer renders from: the frozen offer, then
|
|
53
|
+
`review.open` when the disposition is `review`.
|
|
54
|
+
- **A deferred settlement's route is a function of reason** (control
|
|
55
|
+
ADR-CTRL-540 phase 2). An ending with no launching conversation takes its
|
|
56
|
+
reason's entry in the total per-type `*_DEFERRED_ROUTE_OF_REASON` table:
|
|
57
|
+
`explained` only for a `failed` or `abandoned` ending that offers at least one
|
|
58
|
+
affordance (told fact-free via `toExplainedOutcome`), `notice` for anything
|
|
59
|
+
whose telling needs counts, `covered` when a domain proactive kind normally
|
|
60
|
+
announces it (always with a `fallback: "notice"` and an audience, because
|
|
61
|
+
the backend verifies the covering occurrence at route time), and `silent`
|
|
62
|
+
exactly for `stopped_by_user`. A `produced`, `empty` or `partial` ending is
|
|
63
|
+
never explained.
|
|
64
|
+
- **Workflows never imports proactive.** The covering kinds are literal
|
|
65
|
+
strings pinned against `PROACTIVE_EVENT_KIND_IDS` by a test; the dependency
|
|
66
|
+
runs proactive → workflows only.
|
|
43
67
|
|
|
44
68
|
## Public API
|
|
45
69
|
|
|
@@ -47,8 +71,20 @@ so every producer, validator and renderer agrees on it.
|
|
|
47
71
|
- `WorkflowOutcomeSchema` / `WorkflowOutcome` and the per-type member schemas,
|
|
48
72
|
reason tables, fact schemas and `WorkflowOutcomeDispositionSchema`
|
|
49
73
|
(`outcome.ts`).
|
|
74
|
+
- `DISCOVER_AFFORDANCES_OF_REASON`, `SYNTHESIZE_AFFORDANCES_OF_REASON`,
|
|
75
|
+
`ORG_STRUCTURE_AFFORDANCES_OF_REASON` (`outcome.ts`).
|
|
76
|
+
- `WORKFLOW_AFFORDANCES_OF_REASON`, `affordancesOfReason`,
|
|
77
|
+
`affordancesOfOutcome` (`affordances-of-outcome.ts`).
|
|
78
|
+
- `SETTLEMENT_AUDIENCE_POLICIES`, `SETTLEMENT_COVERING_KINDS`,
|
|
79
|
+
`SETTLEMENT_SILENT_REASONS`, `DEFERRED_ROUTES`, `DeferredRouteSchema` /
|
|
80
|
+
`DeferredRoute`, the `*_DEFERRED_ROUTE_OF_REASON` tables,
|
|
81
|
+
`WORKFLOW_DEFERRED_ROUTE_OF_REASON`, `ExplainedWorkflowOutcome` and its
|
|
82
|
+
`Explained*Reason` types, `deferredRouteOf`, `toExplainedOutcome`
|
|
83
|
+
(`deferred-route.ts`).
|
|
50
84
|
|
|
51
85
|
## Dependencies
|
|
52
86
|
|
|
53
|
-
`zod` (outcome schemas)
|
|
54
|
-
|
|
87
|
+
`zod` (outcome schemas), `../input-requests/types` (the review disposition's
|
|
88
|
+
request type) and `../affordances/affordance` (the offered-affordance schema,
|
|
89
|
+
its sanity bound and `sameAffordances`). Otherwise pure vocabulary data, one
|
|
90
|
+
guard and pure helpers.
|
|
@@ -17,7 +17,24 @@ wire value against it.
|
|
|
17
17
|
(compile-time), each reason table's kinds are valid, the schema accepts a
|
|
18
18
|
well-formed member and refuses a kind that does not match its reason, a
|
|
19
19
|
review disposition on a non-produced outcome, a reason from another type,
|
|
20
|
-
and extra keys (the facts stay closed).
|
|
20
|
+
and extra keys (the facts stay closed). Every reason has an explicit offer
|
|
21
|
+
of offerable affordances, and the schema refuses an offer that differs from
|
|
22
|
+
its reason's table, a missing `affordances` key, `review.open` inside a
|
|
23
|
+
frozen offer and more than `MAX_OFFERED_AFFORDANCES` entries.
|
|
24
|
+
|
|
25
|
+
- `affordances-of-outcome.test.ts` — `affordancesOfOutcome` projects
|
|
26
|
+
`review.open` after the frozen offer for a `review` disposition and nothing
|
|
27
|
+
extra for `narrate`; `affordancesOfReason` returns each workflow type's
|
|
28
|
+
table entry (both org-structure types share one table); every projected
|
|
29
|
+
list is a valid `Affordance` list.
|
|
30
|
+
|
|
31
|
+
- `deferred-route.test.ts` — every reason of every workflow type has a valid
|
|
32
|
+
deferred route; `explained` only for a `failed`/`abandoned` ending with a
|
|
33
|
+
non-empty offer; `produced`/`empty`/`partial` never explained; `silent` ⇔
|
|
34
|
+
`stopped_by_user`; every covering kind is a real proactive kind and every
|
|
35
|
+
covered route falls back to a notice with an audience; `toExplainedOutcome`
|
|
36
|
+
strips facts and stays schema-valid; and no `src/workflows/*.ts` file
|
|
37
|
+
imports from proactive.
|
|
21
38
|
|
|
22
39
|
## Invariants
|
|
23
40
|
|
|
@@ -33,4 +50,6 @@ None — test-only.
|
|
|
33
50
|
|
|
34
51
|
## Dependencies
|
|
35
52
|
|
|
36
|
-
`vitest`
|
|
53
|
+
`vitest`, `node:fs`, `../types`, `../outcome`, `../affordances-of-outcome`,
|
|
54
|
+
`../deferred-route`, `../../affordances/affordance` and
|
|
55
|
+
`../../proactive/kinds` (test-only: pins the covering kinds).
|