@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
|
@@ -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
|
+
});
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
* - A candidate patch changes exactly one thing: bringing a line into review never
|
|
22
22
|
* also commits a treatment, and a patch can never hide a line.
|
|
23
23
|
* - The set response's discovery block is required and null before any completed run.
|
|
24
|
+
* - Index-first discovery (control ADR index-first-operating-knowledge-discovery): an
|
|
25
|
+
* item's operating-knowledge family, canonical path and attention evidence are
|
|
26
|
+
* REQUIRED nullable keys; a family needs a known document type; evidence and a path
|
|
27
|
+
* exist only on a line T1 attended and never on a withheld line.
|
|
28
|
+
* - The discovery block's enumeration fields are all present or all absent, and the
|
|
29
|
+
* run's completeness is the combination of its roots' — a truncated run never
|
|
30
|
+
* claims `complete`.
|
|
24
31
|
*/
|
|
25
32
|
|
|
26
33
|
import { describe, expect, it } from "vitest";
|
|
@@ -59,6 +66,8 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
|
|
|
59
66
|
sourceClass: "board_deck",
|
|
60
67
|
documentType: null,
|
|
61
68
|
documentTypeConfidence: null,
|
|
69
|
+
operatingKnowledgeFamily: null,
|
|
70
|
+
canonicalPath: null,
|
|
62
71
|
periodLabel: "Q3 2026",
|
|
63
72
|
seriesRole: "current",
|
|
64
73
|
relevance: 0.92,
|
|
@@ -74,6 +83,7 @@ function candidateItem(overrides: Record<string, unknown> = {}) {
|
|
|
74
83
|
relevanceReasons: [],
|
|
75
84
|
targetAudience: "org_wide",
|
|
76
85
|
attention: { t1: null, t2: null },
|
|
86
|
+
attentionEvidence: null,
|
|
77
87
|
included: true,
|
|
78
88
|
treatmentUnresolved: false,
|
|
79
89
|
matchReason: "A board deck for the current quarter.",
|
|
@@ -143,6 +153,9 @@ function candidateSet(overrides: Record<string, unknown> = {}) {
|
|
|
143
153
|
filesDropped: 240,
|
|
144
154
|
coveragePercent: 87,
|
|
145
155
|
scanOutcome: "complete",
|
|
156
|
+
enumerationCompleteness: null,
|
|
157
|
+
rootCoverage: null,
|
|
158
|
+
stageCounters: null,
|
|
146
159
|
},
|
|
147
160
|
...overrides,
|
|
148
161
|
};
|
|
@@ -575,6 +588,111 @@ describe("candidate relevance", () => {
|
|
|
575
588
|
});
|
|
576
589
|
});
|
|
577
590
|
|
|
591
|
+
describe("index-first discovery item fields", () => {
|
|
592
|
+
const evidence = [
|
|
593
|
+
{
|
|
594
|
+
kind: "path_terms",
|
|
595
|
+
values: ["playbook"],
|
|
596
|
+
source: { producer: "lexicon", version: "3" },
|
|
597
|
+
},
|
|
598
|
+
];
|
|
599
|
+
|
|
600
|
+
it("an item carries its operating-knowledge family, canonical path and attention evidence", () => {
|
|
601
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
602
|
+
candidateItem({
|
|
603
|
+
documentType: "process_playbook",
|
|
604
|
+
documentTypeConfidence: 0.88,
|
|
605
|
+
operatingKnowledgeFamily: "practices",
|
|
606
|
+
canonicalPath: "Product/How we work",
|
|
607
|
+
attention: { t1: "exploit", t2: null },
|
|
608
|
+
attentionEvidence: evidence,
|
|
609
|
+
}),
|
|
610
|
+
);
|
|
611
|
+
expect(parsed.success).toBe(true);
|
|
612
|
+
expect(parsed.data?.operatingKnowledgeFamily).toBe("practices");
|
|
613
|
+
expect(parsed.data?.canonicalPath).toBe("Product/How we work");
|
|
614
|
+
expect(parsed.data?.attentionEvidence).toEqual(evidence);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("an item from before index-first discovery has no family, no path and no evidence", () => {
|
|
618
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(candidateItem());
|
|
619
|
+
expect(parsed.success).toBe(true);
|
|
620
|
+
expect(parsed.data?.operatingKnowledgeFamily).toBeNull();
|
|
621
|
+
expect(parsed.data?.canonicalPath).toBeNull();
|
|
622
|
+
expect(parsed.data?.attentionEvidence).toBeNull();
|
|
623
|
+
for (const key of [
|
|
624
|
+
"operatingKnowledgeFamily",
|
|
625
|
+
"canonicalPath",
|
|
626
|
+
"attentionEvidence",
|
|
627
|
+
]) {
|
|
628
|
+
const item: Record<string, unknown> = candidateItem();
|
|
629
|
+
delete item[key];
|
|
630
|
+
expect(SourceSyncCandidateItemSchema.safeParse(item).success).toBe(false);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it("an operating-knowledge family needs a known document type", () => {
|
|
635
|
+
for (const [documentType, documentTypeConfidence] of [
|
|
636
|
+
[null, null],
|
|
637
|
+
["unknown", null],
|
|
638
|
+
] as const) {
|
|
639
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
640
|
+
candidateItem({
|
|
641
|
+
documentType,
|
|
642
|
+
documentTypeConfidence,
|
|
643
|
+
operatingKnowledgeFamily: "practices",
|
|
644
|
+
}),
|
|
645
|
+
);
|
|
646
|
+
expect(parsed.success).toBe(false);
|
|
647
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
648
|
+
"an operating-knowledge family needs a known document type",
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("a withheld line carries no attention evidence and no path", () => {
|
|
654
|
+
for (const claim of [
|
|
655
|
+
{ attentionEvidence: evidence },
|
|
656
|
+
{ canonicalPath: "Finance/Board" },
|
|
657
|
+
]) {
|
|
658
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
659
|
+
candidateItem({
|
|
660
|
+
withheld: true,
|
|
661
|
+
attention: { t1: "exploit", t2: null },
|
|
662
|
+
...claim,
|
|
663
|
+
}),
|
|
664
|
+
);
|
|
665
|
+
expect(parsed.success).toBe(false);
|
|
666
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
667
|
+
"a withheld line carries no attention evidence and no path",
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("attention evidence exists only on a line T1 attended", () => {
|
|
673
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
674
|
+
candidateItem({
|
|
675
|
+
attention: { t1: null, t2: "explore" },
|
|
676
|
+
attentionEvidence: evidence,
|
|
677
|
+
}),
|
|
678
|
+
);
|
|
679
|
+
expect(parsed.success).toBe(false);
|
|
680
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
681
|
+
"attention evidence exists only on a line T1 attended",
|
|
682
|
+
);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it("a canonical path exists only on a line T1 attended", () => {
|
|
686
|
+
const parsed = SourceSyncCandidateItemSchema.safeParse(
|
|
687
|
+
candidateItem({ canonicalPath: "Product/How we work" }),
|
|
688
|
+
);
|
|
689
|
+
expect(parsed.success).toBe(false);
|
|
690
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
691
|
+
"a canonical path exists only on a line T1 attended",
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
578
696
|
describe("SourceSyncCandidateItemPatchSchema", () => {
|
|
579
697
|
it("bring into review is a patch of visibility alone", () => {
|
|
580
698
|
expect(
|
|
@@ -631,6 +749,111 @@ describe("SourceSyncCandidateSetResponseSchema discovery", () => {
|
|
|
631
749
|
).toBe(false);
|
|
632
750
|
}
|
|
633
751
|
});
|
|
752
|
+
|
|
753
|
+
const rootEntry = (status: string, rootId: string | null = null) => ({
|
|
754
|
+
rootKind: rootId === null ? "user" : "shared_drive",
|
|
755
|
+
rootId,
|
|
756
|
+
status,
|
|
757
|
+
enumeratedCount: 3,
|
|
758
|
+
observedOldestModifiedTime: "2024-01-01T00:00:00Z",
|
|
759
|
+
observedNewestModifiedTime: "2026-09-01T00:00:00Z",
|
|
760
|
+
skippedRanges: [],
|
|
761
|
+
});
|
|
762
|
+
const stageCounters = {
|
|
763
|
+
filesEnumerated: 100000,
|
|
764
|
+
filesMetadataEligible: 60000,
|
|
765
|
+
filesT0Scored: 60000,
|
|
766
|
+
filesT1Attended: 400,
|
|
767
|
+
filesPeeked: 120,
|
|
768
|
+
filesClassified: 300,
|
|
769
|
+
filesShown: 12,
|
|
770
|
+
filesRetained: 40,
|
|
771
|
+
};
|
|
772
|
+
const withEnumeration = (enumeration: Record<string, unknown>) =>
|
|
773
|
+
candidateSet({
|
|
774
|
+
discovery: { ...candidateSet().discovery, ...enumeration },
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it("a discovery block from before index-first discovery has no enumeration fields", () => {
|
|
778
|
+
const parsed =
|
|
779
|
+
SourceSyncCandidateSetResponseSchema.safeParse(candidateSet());
|
|
780
|
+
expect(parsed.success).toBe(true);
|
|
781
|
+
expect(parsed.data?.discovery?.enumerationCompleteness).toBeNull();
|
|
782
|
+
expect(parsed.data?.discovery?.rootCoverage).toBeNull();
|
|
783
|
+
expect(parsed.data?.discovery?.stageCounters).toBeNull();
|
|
784
|
+
for (const key of [
|
|
785
|
+
"enumerationCompleteness",
|
|
786
|
+
"rootCoverage",
|
|
787
|
+
"stageCounters",
|
|
788
|
+
]) {
|
|
789
|
+
const discovery: Record<string, unknown> = {
|
|
790
|
+
...candidateSet().discovery,
|
|
791
|
+
};
|
|
792
|
+
delete discovery[key];
|
|
793
|
+
expect(
|
|
794
|
+
SourceSyncCandidateSetResponseSchema.safeParse(
|
|
795
|
+
candidateSet({ discovery }),
|
|
796
|
+
).success,
|
|
797
|
+
).toBe(false);
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it("enumeration fields are all present or all absent", () => {
|
|
802
|
+
expect(
|
|
803
|
+
SourceSyncCandidateSetResponseSchema.safeParse(
|
|
804
|
+
withEnumeration({
|
|
805
|
+
enumerationCompleteness: "complete",
|
|
806
|
+
rootCoverage: [rootEntry("complete")],
|
|
807
|
+
stageCounters,
|
|
808
|
+
}),
|
|
809
|
+
).success,
|
|
810
|
+
).toBe(true);
|
|
811
|
+
for (const partial of [
|
|
812
|
+
{ enumerationCompleteness: "complete" },
|
|
813
|
+
{ enumerationCompleteness: "complete", stageCounters },
|
|
814
|
+
{ rootCoverage: [rootEntry("complete")], stageCounters },
|
|
815
|
+
]) {
|
|
816
|
+
const parsed = SourceSyncCandidateSetResponseSchema.safeParse(
|
|
817
|
+
withEnumeration(partial),
|
|
818
|
+
);
|
|
819
|
+
expect(parsed.success).toBe(false);
|
|
820
|
+
expect(parsed.error?.issues[0]?.message).toBe(
|
|
821
|
+
"enumeration fields are all present or all absent",
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it("enumeration completeness is the combination of its roots", () => {
|
|
827
|
+
const rootCoverage = [
|
|
828
|
+
rootEntry("complete"),
|
|
829
|
+
rootEntry("local_budget_truncated", "0AbCdEfGhIjK"),
|
|
830
|
+
];
|
|
831
|
+
const claimsComplete = SourceSyncCandidateSetResponseSchema.safeParse(
|
|
832
|
+
withEnumeration({
|
|
833
|
+
enumerationCompleteness: "complete",
|
|
834
|
+
rootCoverage,
|
|
835
|
+
stageCounters,
|
|
836
|
+
}),
|
|
837
|
+
);
|
|
838
|
+
expect(claimsComplete.success).toBe(false);
|
|
839
|
+
expect(claimsComplete.error?.issues[0]?.message).toBe(
|
|
840
|
+
"enumeration completeness is the combination of its roots",
|
|
841
|
+
);
|
|
842
|
+
const truncated = SourceSyncCandidateSetResponseSchema.safeParse(
|
|
843
|
+
withEnumeration({
|
|
844
|
+
enumerationCompleteness: "local_budget_truncated",
|
|
845
|
+
rootCoverage,
|
|
846
|
+
stageCounters,
|
|
847
|
+
}),
|
|
848
|
+
);
|
|
849
|
+
expect(truncated.success).toBe(true);
|
|
850
|
+
expect(truncated.data?.discovery?.stageCounters?.filesEnumerated).toBe(
|
|
851
|
+
100000,
|
|
852
|
+
);
|
|
853
|
+
expect(truncated.data?.discovery?.enumerationCompleteness).not.toBe(
|
|
854
|
+
"complete",
|
|
855
|
+
);
|
|
856
|
+
});
|
|
634
857
|
});
|
|
635
858
|
|
|
636
859
|
describe("SourceSyncDiscoveryRunResponseSchema", () => {
|
|
@@ -6,18 +6,31 @@
|
|
|
6
6
|
* order.
|
|
7
7
|
*
|
|
8
8
|
* INVARIANTS TESTED:
|
|
9
|
-
* - Each of the twenty-
|
|
10
|
-
* relevance) has exactly the members this release
|
|
9
|
+
* - Each of the twenty-five closed sets (eleven Source Sync, thirteen discovery
|
|
10
|
+
* relevance, one attention evidence) has exactly the members this release
|
|
11
|
+
* fixes
|
|
11
12
|
* - `decoy` is a source class; `unknown` is a sharing breadth
|
|
13
|
+
* - OPERATING_KNOWLEDGE_FAMILIES names exactly the five families
|
|
12
14
|
* - No member string appears in two sets
|
|
13
15
|
* - The four relevance reason code sets are pairwise disjoint
|
|
14
|
-
* - DOCUMENT_TYPES has exactly its
|
|
16
|
+
* - DOCUMENT_TYPES has exactly its forty members, the ten operating-knowledge
|
|
17
|
+
* types immediately before `unknown`, `unknown` last; it sits
|
|
15
18
|
* OUTSIDE the disjointness table and shares a word with another set only
|
|
16
19
|
* where pinned (a structural name on a separate axis)
|
|
20
|
+
* - ENUMERATION_COMPLETENESS and ENUMERATION_ROOT_KINDS sit OUTSIDE the
|
|
21
|
+
* disjointness table: `complete` is also a scan outcome and `domain` a
|
|
22
|
+
* sharing breadth (same word, separate axis), pinned exactly
|
|
17
23
|
*/
|
|
18
24
|
|
|
19
25
|
import { describe, it, expect } from "vitest";
|
|
20
26
|
|
|
27
|
+
import { ATTENTION_SIGNAL_KINDS } from "../attention-evidence";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
ENUMERATION_COMPLETENESS,
|
|
31
|
+
ENUMERATION_ROOT_KINDS,
|
|
32
|
+
} from "../enumeration";
|
|
33
|
+
|
|
21
34
|
import {
|
|
22
35
|
ATTENTION_LANES,
|
|
23
36
|
CANDIDATE_MACHINE_STATES,
|
|
@@ -37,6 +50,7 @@ import {
|
|
|
37
50
|
import {
|
|
38
51
|
AUDIENCE_FITS,
|
|
39
52
|
DOCUMENT_TYPES,
|
|
53
|
+
OPERATING_KNOWLEDGE_FAMILIES,
|
|
40
54
|
SHARING_BREADTHS,
|
|
41
55
|
SOURCE_CLASSES,
|
|
42
56
|
SOURCE_SENSITIVITY_CLASSES,
|
|
@@ -126,6 +140,17 @@ const VOCABULARIES = [
|
|
|
126
140
|
tuple: SOURCE_SYNC_SCOPE_APPROVER_KINDS,
|
|
127
141
|
members: ["org_admin", "unit_head"],
|
|
128
142
|
},
|
|
143
|
+
{
|
|
144
|
+
name: "OPERATING_KNOWLEDGE_FAMILIES",
|
|
145
|
+
tuple: OPERATING_KNOWLEDGE_FAMILIES,
|
|
146
|
+
members: [
|
|
147
|
+
"direction",
|
|
148
|
+
"operating_model",
|
|
149
|
+
"practices",
|
|
150
|
+
"identity_market",
|
|
151
|
+
"decisions_learning",
|
|
152
|
+
],
|
|
153
|
+
},
|
|
129
154
|
{
|
|
130
155
|
name: "SOURCE_RELEVANCE_TIERS",
|
|
131
156
|
tuple: SOURCE_RELEVANCE_TIERS,
|
|
@@ -240,6 +265,18 @@ const VOCABULARIES = [
|
|
|
240
265
|
tuple: SCAN_OUTCOMES,
|
|
241
266
|
members: ["complete", "budget_exhausted", "file_ceiling", "stopped"],
|
|
242
267
|
},
|
|
268
|
+
{
|
|
269
|
+
name: "ATTENTION_SIGNAL_KINDS",
|
|
270
|
+
tuple: ATTENTION_SIGNAL_KINDS,
|
|
271
|
+
members: [
|
|
272
|
+
"tree_prior",
|
|
273
|
+
"path_terms",
|
|
274
|
+
"content_match",
|
|
275
|
+
"breadth",
|
|
276
|
+
"recency",
|
|
277
|
+
"mime_prior",
|
|
278
|
+
],
|
|
279
|
+
},
|
|
243
280
|
] as const satisfies readonly {
|
|
244
281
|
name: string;
|
|
245
282
|
tuple: readonly string[];
|
|
@@ -274,6 +311,16 @@ describe("Source Sync closed sets", () => {
|
|
|
274
311
|
expect(SHARING_BREADTHS).toContain("unknown");
|
|
275
312
|
});
|
|
276
313
|
|
|
314
|
+
it("names the five operating-knowledge families", () => {
|
|
315
|
+
expect([...OPERATING_KNOWLEDGE_FAMILIES]).toEqual([
|
|
316
|
+
"direction",
|
|
317
|
+
"operating_model",
|
|
318
|
+
"practices",
|
|
319
|
+
"identity_market",
|
|
320
|
+
"decisions_learning",
|
|
321
|
+
]);
|
|
322
|
+
});
|
|
323
|
+
|
|
277
324
|
it("no member appears in two sets", () => {
|
|
278
325
|
const all = VOCABULARIES.flatMap(({ tuple }) => [...tuple]);
|
|
279
326
|
expect(new Set(all).size).toBe(all.length);
|
|
@@ -295,7 +342,7 @@ describe("Source Sync closed sets", () => {
|
|
|
295
342
|
});
|
|
296
343
|
|
|
297
344
|
describe("document types", () => {
|
|
298
|
-
it("has exactly the
|
|
345
|
+
it("has exactly the forty document types, in order", () => {
|
|
299
346
|
expect([...DOCUMENT_TYPES]).toEqual([
|
|
300
347
|
"board_deck",
|
|
301
348
|
"board_minutes",
|
|
@@ -326,9 +373,34 @@ describe("document types", () => {
|
|
|
326
373
|
"coursework",
|
|
327
374
|
"empty_template",
|
|
328
375
|
"image_or_screenshot",
|
|
376
|
+
"team_charter",
|
|
377
|
+
"process_playbook",
|
|
378
|
+
"policy_handbook",
|
|
379
|
+
"onboarding_guide",
|
|
380
|
+
"values_culture",
|
|
381
|
+
"positioning_messaging",
|
|
382
|
+
"decision_log",
|
|
383
|
+
"postmortem",
|
|
384
|
+
"metrics_definition",
|
|
385
|
+
"operating_cadence",
|
|
329
386
|
"unknown",
|
|
330
387
|
]);
|
|
331
|
-
expect(new Set(DOCUMENT_TYPES).size).toBe(
|
|
388
|
+
expect(new Set(DOCUMENT_TYPES).size).toBe(40);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("the ten operating-knowledge document types sit before unknown", () => {
|
|
392
|
+
expect(DOCUMENT_TYPES.slice(29, 39)).toEqual([
|
|
393
|
+
"team_charter",
|
|
394
|
+
"process_playbook",
|
|
395
|
+
"policy_handbook",
|
|
396
|
+
"onboarding_guide",
|
|
397
|
+
"values_culture",
|
|
398
|
+
"positioning_messaging",
|
|
399
|
+
"decision_log",
|
|
400
|
+
"postmortem",
|
|
401
|
+
"metrics_definition",
|
|
402
|
+
"operating_cadence",
|
|
403
|
+
]);
|
|
332
404
|
});
|
|
333
405
|
|
|
334
406
|
it("unknown is the last document type", () => {
|
|
@@ -349,3 +421,30 @@ describe("document types", () => {
|
|
|
349
421
|
]);
|
|
350
422
|
});
|
|
351
423
|
});
|
|
424
|
+
|
|
425
|
+
describe("enumeration vocabulary", () => {
|
|
426
|
+
it("enumeration vocabularies share a member with another set only where pinned", () => {
|
|
427
|
+
const others = new Set<string>(
|
|
428
|
+
VOCABULARIES.flatMap(({ tuple }) => [...tuple]),
|
|
429
|
+
);
|
|
430
|
+
// SCAN_OUTCOMES
|
|
431
|
+
expect(ENUMERATION_COMPLETENESS.filter((m) => others.has(m))).toEqual([
|
|
432
|
+
"complete",
|
|
433
|
+
]);
|
|
434
|
+
// SHARING_BREADTHS
|
|
435
|
+
expect(ENUMERATION_ROOT_KINDS.filter((m) => others.has(m))).toEqual([
|
|
436
|
+
"domain",
|
|
437
|
+
]);
|
|
438
|
+
expect([...ENUMERATION_COMPLETENESS]).toEqual([
|
|
439
|
+
"complete",
|
|
440
|
+
"local_budget_truncated",
|
|
441
|
+
"provider_incomplete",
|
|
442
|
+
"both",
|
|
443
|
+
]);
|
|
444
|
+
expect([...ENUMERATION_ROOT_KINDS]).toEqual([
|
|
445
|
+
"user",
|
|
446
|
+
"shared_drive",
|
|
447
|
+
"domain",
|
|
448
|
+
]);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHY a file received T1 attention — the evidence vector, each signal with its
|
|
3
|
+
* PROVENANCE (control ADR index-first-operating-knowledge-discovery;
|
|
4
|
+
* INV-SOURCESYNC-ATTENTION-EVIDENCE-HAS-PROVENANCE).
|
|
5
|
+
*
|
|
6
|
+
* It exists only for a file that crossed T1 attention: pre-attention signals are
|
|
7
|
+
* ephemeral (INV-SOURCESYNC-PRE-ATTENTION-SIGNALS-ARE-EPHEMERAL), so a file that never
|
|
8
|
+
* reached attention carries NULL evidence. Every signal names the producer and version
|
|
9
|
+
* that judged it, so "why did c_S find this?" stays answerable after prompts and
|
|
10
|
+
* lexicons change. `producer` is a documented key string (not a closed set, so a new
|
|
11
|
+
* producer needs no contracts major); the producers in use are `lexicon`,
|
|
12
|
+
* `source-discovery-tree-triage`, `source-discovery-plan` and `metadata-scorer`.
|
|
13
|
+
*
|
|
14
|
+
* TERMS are product- or plan-proposed vocabulary that MATCHED — never a slice of the
|
|
15
|
+
* file's name and never text read from the file. A folder is named by its provider
|
|
16
|
+
* id (`folderId`), never its name (INV-SOURCESYNC-TOPOLOGY-IS-NOT-SOURCE-EVIDENCE).
|
|
17
|
+
*/
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { SHARING_BREADTHS } from "./vocabulary";
|
|
20
|
+
|
|
21
|
+
export const ATTENTION_SIGNAL_KINDS = [
|
|
22
|
+
"tree_prior",
|
|
23
|
+
"path_terms",
|
|
24
|
+
"content_match",
|
|
25
|
+
"breadth",
|
|
26
|
+
"recency",
|
|
27
|
+
"mime_prior",
|
|
28
|
+
] as const;
|
|
29
|
+
export type AttentionSignalKind = (typeof ATTENTION_SIGNAL_KINDS)[number];
|
|
30
|
+
|
|
31
|
+
/** Who judged a signal: a producer key (a prompt key, `lexicon`, a scorer) and its version. */
|
|
32
|
+
export const AttentionSignalSourceSchema = z.object({
|
|
33
|
+
producer: z.string().regex(/^[a-z0-9][a-z0-9.-]{0,63}$/),
|
|
34
|
+
version: z.string().min(1).max(32),
|
|
35
|
+
});
|
|
36
|
+
export type AttentionSignalSource = z.infer<typeof AttentionSignalSourceSchema>;
|
|
37
|
+
|
|
38
|
+
const Score = z.number().min(0).max(1);
|
|
39
|
+
const Terms = z.array(z.string().min(1).max(64)).min(1).max(16);
|
|
40
|
+
|
|
41
|
+
export const AttentionSignalSchema = z.discriminatedUnion("kind", [
|
|
42
|
+
z.object({
|
|
43
|
+
kind: z.literal("tree_prior"),
|
|
44
|
+
score: Score,
|
|
45
|
+
/** The provider id of the folder the tree triage ranked — never its name. */
|
|
46
|
+
folderId: z.string().min(1).max(128),
|
|
47
|
+
source: AttentionSignalSourceSchema,
|
|
48
|
+
}),
|
|
49
|
+
z.object({
|
|
50
|
+
kind: z.literal("path_terms"),
|
|
51
|
+
values: Terms,
|
|
52
|
+
source: AttentionSignalSourceSchema,
|
|
53
|
+
}),
|
|
54
|
+
z.object({
|
|
55
|
+
kind: z.literal("content_match"),
|
|
56
|
+
values: Terms,
|
|
57
|
+
strength: Score,
|
|
58
|
+
/** The structured content probe that matched. */
|
|
59
|
+
probeId: z.string().min(1).max(64),
|
|
60
|
+
source: AttentionSignalSourceSchema,
|
|
61
|
+
}),
|
|
62
|
+
z.object({
|
|
63
|
+
kind: z.literal("breadth"),
|
|
64
|
+
value: z.enum(SHARING_BREADTHS),
|
|
65
|
+
source: AttentionSignalSourceSchema,
|
|
66
|
+
}),
|
|
67
|
+
z.object({
|
|
68
|
+
kind: z.literal("recency"),
|
|
69
|
+
score: Score,
|
|
70
|
+
source: AttentionSignalSourceSchema,
|
|
71
|
+
}),
|
|
72
|
+
z.object({
|
|
73
|
+
kind: z.literal("mime_prior"),
|
|
74
|
+
score: Score,
|
|
75
|
+
source: AttentionSignalSourceSchema,
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
export type AttentionSignal = z.infer<typeof AttentionSignalSchema>;
|
|
79
|
+
|
|
80
|
+
/** At most one signal of each kind. */
|
|
81
|
+
export const AttentionEvidenceSchema = z
|
|
82
|
+
.array(AttentionSignalSchema)
|
|
83
|
+
.max(ATTENTION_SIGNAL_KINDS.length)
|
|
84
|
+
.superRefine((signals, ctx) => {
|
|
85
|
+
const kinds = signals.map((s) => s.kind);
|
|
86
|
+
if (new Set(kinds).size !== kinds.length) {
|
|
87
|
+
ctx.addIssue({
|
|
88
|
+
code: "custom",
|
|
89
|
+
message: "an attention signal kind appears at most once",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
export type AttentionEvidence = z.infer<typeof AttentionEvidenceSchema>;
|