@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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The affordance vocabulary: every next step the product may OFFER a person
|
|
3
|
+
* or the agent (control ADR affordance-system).
|
|
4
|
+
*
|
|
5
|
+
* An affordance names an action the product can actually perform. Existing
|
|
6
|
+
* here is never a reason to offer it: an affordance is offered only by a
|
|
7
|
+
* domain-owned, deterministic offer rule that relates a domain state to it
|
|
8
|
+
* (INV-AFFORD-RELEVANT). Neither a model nor a renderer invents, infers or
|
|
9
|
+
* searches for one.
|
|
10
|
+
*
|
|
11
|
+
* INVARIANTS:
|
|
12
|
+
* - `{domain}.{action}` names, camelCase after the dot. A member is persisted
|
|
13
|
+
* inside immutable workflow outcomes and chat parts, so the set is CLOSED and
|
|
14
|
+
* append-only: removing one orphans every record that carries it.
|
|
15
|
+
* - GROUNDED. Every member has a binding kind (`AFFORDANCE_BINDING_KIND_OF` in
|
|
16
|
+
* ./affordance) and the repo that owns the binding proves it exists with an
|
|
17
|
+
* architecture test: a mounted backend route for `command`, an app `Route`
|
|
18
|
+
* for `navigate`, an app launch surface for `surface` (INV-AFFORD-GROUNDED).
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
export const AFFORDANCE_IDS = [
|
|
23
|
+
/** Start a new search of the connected drive (Source Sync discovery). */
|
|
24
|
+
"sourceSync.findFiles",
|
|
25
|
+
/** Connect Google Drive, or reconnect it when its sign-in stopped working. */
|
|
26
|
+
"integrations.connectGoogleDrive",
|
|
27
|
+
/** Turn Source Sync on for the org. */
|
|
28
|
+
"sourceSync.enable",
|
|
29
|
+
/** Open the org's billing page. */
|
|
30
|
+
"billing.open",
|
|
31
|
+
/** Run the org-structure analysis again. */
|
|
32
|
+
"orgStructure.reanalyze",
|
|
33
|
+
/** Sync the HR directory again. */
|
|
34
|
+
"hris.resyncDirectory",
|
|
35
|
+
/**
|
|
36
|
+
* Open a review (an input request) in chat. PROJECTED, never persisted in an
|
|
37
|
+
* offer: it is derived from authoritative state already stored elsewhere (a
|
|
38
|
+
* workflow outcome's `review` disposition). See PROJECTED_AFFORDANCE_IDS.
|
|
39
|
+
*/
|
|
40
|
+
"review.open",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
export type AffordanceId = (typeof AFFORDANCE_IDS)[number];
|
|
44
|
+
|
|
45
|
+
export const AffordanceIdSchema = z.enum(AFFORDANCE_IDS);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Affordance vocabulary barrel (control ADR affordance-system).
|
|
3
|
+
* Import from '@company-semantics/contracts' (root).
|
|
4
|
+
*/
|
|
5
|
+
export { AFFORDANCE_IDS, AffordanceIdSchema } from "./ids";
|
|
6
|
+
export type { AffordanceId } from "./ids";
|
|
7
|
+
export {
|
|
8
|
+
AFFORDANCE_ARGS,
|
|
9
|
+
NO_ARG_AFFORDANCE_IDS,
|
|
10
|
+
ReviewOpenArgsSchema,
|
|
11
|
+
} from "./args";
|
|
12
|
+
export type { AffordanceArgs } from "./args";
|
|
13
|
+
export {
|
|
14
|
+
AFFORDANCE_AVAILABILITY_IDS,
|
|
15
|
+
AFFORDANCE_BINDING_KINDS,
|
|
16
|
+
AFFORDANCE_UNAVAILABLE_REASONS,
|
|
17
|
+
AffordanceAvailabilityIdSchema,
|
|
18
|
+
AffordanceAvailabilitySchema,
|
|
19
|
+
} from "./availability";
|
|
20
|
+
export type {
|
|
21
|
+
AffordanceAvailability,
|
|
22
|
+
AffordanceAvailabilityId,
|
|
23
|
+
AffordanceBindingKind,
|
|
24
|
+
AffordanceUnavailableReason,
|
|
25
|
+
} from "./availability";
|
|
26
|
+
export {
|
|
27
|
+
AFFORDANCE_AVAILABILITY_OF,
|
|
28
|
+
AFFORDANCE_BINDING_KIND_OF,
|
|
29
|
+
AffordanceSchema,
|
|
30
|
+
MAX_OFFERED_AFFORDANCES,
|
|
31
|
+
OfferedAffordanceSchema,
|
|
32
|
+
PROJECTED_AFFORDANCE_IDS,
|
|
33
|
+
sameAffordances,
|
|
34
|
+
} from "./affordance";
|
|
35
|
+
export type {
|
|
36
|
+
Affordance,
|
|
37
|
+
OfferedAffordance,
|
|
38
|
+
ProjectedAffordanceId,
|
|
39
|
+
} from "./affordance";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = 'b2a6eec5dea4' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = 'b2a6eec5dea4f72290740a1cf16608a826ece2e3654ba64d2bc5e3794d571809' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -4271,6 +4271,23 @@ export interface paths {
|
|
|
4271
4271
|
patch?: never;
|
|
4272
4272
|
trace?: never;
|
|
4273
4273
|
};
|
|
4274
|
+
"/api/internal-admin/quality/decisions/{id}/diagnosis": {
|
|
4275
|
+
parameters: {
|
|
4276
|
+
query?: never;
|
|
4277
|
+
header?: never;
|
|
4278
|
+
path?: never;
|
|
4279
|
+
cookie?: never;
|
|
4280
|
+
};
|
|
4281
|
+
/** A decision's diagnosis: stable vs flaky failures, failure kinds by bucket, suspected component and findings */
|
|
4282
|
+
get: operations["getQualityDecisionDiagnosis"];
|
|
4283
|
+
put?: never;
|
|
4284
|
+
post?: never;
|
|
4285
|
+
delete?: never;
|
|
4286
|
+
options?: never;
|
|
4287
|
+
head?: never;
|
|
4288
|
+
patch?: never;
|
|
4289
|
+
trace?: never;
|
|
4290
|
+
};
|
|
4274
4291
|
"/api/comments": {
|
|
4275
4292
|
parameters: {
|
|
4276
4293
|
query?: never;
|
|
@@ -8122,7 +8139,7 @@ export interface components {
|
|
|
8122
8139
|
notification: {
|
|
8123
8140
|
id: string;
|
|
8124
8141
|
/** @enum {string} */
|
|
8125
|
-
kind: "companyMd.access_requested" | "companyMd.access_request_approved" | "companyMd.access_request_denied" | "comment.mention" | "comment.reply" | "chat.mention" | "proactive.org_became_shared" | "proactive.brief_morning" | "proactive.billing_balance_below_half" | "proactive.billing_balance_below_threshold" | "proactive.billing_auto_recharge_succeeded" | "proactive.billing_top_up_credited" | "proactive.billing_monthly_budget_80" | "proactive.billing_monthly_budget_100" | "proactive.billing_user_monthly_limit_reached" | "proactive.billing_byo_key_invalid" | "proactive.billing_provider_price_changed" | "proactive.billing_payment_recovered" | "proactive.billing_balance_exhausted" | "proactive.billing_auto_recharge_failed" | "proactive.billing_payment_past_due" | "proactive.billing_suspended" | "proactive.billing_subscription_canceled" | "proactive.source_sync_docs_updated" | "proactive.source_sync_review_ready" | "proactive.org_structure_review_ready";
|
|
8142
|
+
kind: "companyMd.access_requested" | "companyMd.access_request_approved" | "companyMd.access_request_denied" | "comment.mention" | "comment.reply" | "chat.mention" | "proactive.org_became_shared" | "proactive.brief_morning" | "proactive.billing_balance_below_half" | "proactive.billing_balance_below_threshold" | "proactive.billing_auto_recharge_succeeded" | "proactive.billing_top_up_credited" | "proactive.billing_monthly_budget_80" | "proactive.billing_monthly_budget_100" | "proactive.billing_user_monthly_limit_reached" | "proactive.billing_byo_key_invalid" | "proactive.billing_provider_price_changed" | "proactive.billing_payment_recovered" | "proactive.billing_balance_exhausted" | "proactive.billing_auto_recharge_failed" | "proactive.billing_payment_past_due" | "proactive.billing_suspended" | "proactive.billing_subscription_canceled" | "proactive.source_sync_docs_updated" | "proactive.source_sync_review_ready" | "proactive.org_structure_review_ready" | "proactive.workflow_needs_attention" | "proactive.workflow_finished";
|
|
8126
8143
|
target: {
|
|
8127
8144
|
/** @enum {string} */
|
|
8128
8145
|
type: "company_md" | "org_unit" | "org" | "execution";
|
|
@@ -9328,8 +9345,10 @@ export interface components {
|
|
|
9328
9345
|
/** @enum {string} */
|
|
9329
9346
|
classificationState: "unclassified" | "classified";
|
|
9330
9347
|
sourceClass: ("board_deck" | "board_minutes" | "investor_update" | "financial_brief" | "budget_forecast" | "okr_plan" | "annual_plan" | "all_hands" | "unit_strategy" | "roadmap" | "qbr" | "other" | "decoy") | null;
|
|
9331
|
-
documentType: ("board_deck" | "board_minutes" | "strategy_memo" | "planning_doc" | "okr_doc" | "roadmap" | "operating_review" | "all_hands_deck" | "offsite_notes" | "investor_update" | "pitch_deck" | "product_spec" | "research_analysis" | "general_meeting_notes" | "financial_plan" | "financial_actuals" | "equity_admin_record" | "corporate_formation_record" | "corporate_governance_record" | "government_filing" | "equity_agreement" | "commercial_agreement" | "employment_agreement" | "transaction_record" | "personal_document" | "social_event_material" | "coursework" | "empty_template" | "image_or_screenshot" | "unknown") | null;
|
|
9348
|
+
documentType: ("board_deck" | "board_minutes" | "strategy_memo" | "planning_doc" | "okr_doc" | "roadmap" | "operating_review" | "all_hands_deck" | "offsite_notes" | "investor_update" | "pitch_deck" | "product_spec" | "research_analysis" | "general_meeting_notes" | "financial_plan" | "financial_actuals" | "equity_admin_record" | "corporate_formation_record" | "corporate_governance_record" | "government_filing" | "equity_agreement" | "commercial_agreement" | "employment_agreement" | "transaction_record" | "personal_document" | "social_event_material" | "coursework" | "empty_template" | "image_or_screenshot" | "team_charter" | "process_playbook" | "policy_handbook" | "onboarding_guide" | "values_culture" | "positioning_messaging" | "decision_log" | "postmortem" | "metrics_definition" | "operating_cadence" | "unknown") | null;
|
|
9332
9349
|
documentTypeConfidence: number | null;
|
|
9350
|
+
operatingKnowledgeFamily: ("direction" | "operating_model" | "practices" | "identity_market" | "decisions_learning") | null;
|
|
9351
|
+
canonicalPath: string | null;
|
|
9333
9352
|
periodLabel: string | null;
|
|
9334
9353
|
seriesRole: ("current" | "history") | null;
|
|
9335
9354
|
relevance: number;
|
|
@@ -9375,6 +9394,59 @@ export interface components {
|
|
|
9375
9394
|
t1: ("exploit" | "explore") | null;
|
|
9376
9395
|
t2: ("exploit" | "explore") | null;
|
|
9377
9396
|
};
|
|
9397
|
+
attentionEvidence: ({
|
|
9398
|
+
/** @constant */
|
|
9399
|
+
kind: "tree_prior";
|
|
9400
|
+
score: number;
|
|
9401
|
+
folderId: string;
|
|
9402
|
+
source: {
|
|
9403
|
+
producer: string;
|
|
9404
|
+
version: string;
|
|
9405
|
+
};
|
|
9406
|
+
} | {
|
|
9407
|
+
/** @constant */
|
|
9408
|
+
kind: "path_terms";
|
|
9409
|
+
values: string[];
|
|
9410
|
+
source: {
|
|
9411
|
+
producer: string;
|
|
9412
|
+
version: string;
|
|
9413
|
+
};
|
|
9414
|
+
} | {
|
|
9415
|
+
/** @constant */
|
|
9416
|
+
kind: "content_match";
|
|
9417
|
+
values: string[];
|
|
9418
|
+
strength: number;
|
|
9419
|
+
probeId: string;
|
|
9420
|
+
source: {
|
|
9421
|
+
producer: string;
|
|
9422
|
+
version: string;
|
|
9423
|
+
};
|
|
9424
|
+
} | {
|
|
9425
|
+
/** @constant */
|
|
9426
|
+
kind: "breadth";
|
|
9427
|
+
/** @enum {string} */
|
|
9428
|
+
value: "restricted_few" | "group" | "domain" | "anyone" | "unknown";
|
|
9429
|
+
source: {
|
|
9430
|
+
producer: string;
|
|
9431
|
+
version: string;
|
|
9432
|
+
};
|
|
9433
|
+
} | {
|
|
9434
|
+
/** @constant */
|
|
9435
|
+
kind: "recency";
|
|
9436
|
+
score: number;
|
|
9437
|
+
source: {
|
|
9438
|
+
producer: string;
|
|
9439
|
+
version: string;
|
|
9440
|
+
};
|
|
9441
|
+
} | {
|
|
9442
|
+
/** @constant */
|
|
9443
|
+
kind: "mime_prior";
|
|
9444
|
+
score: number;
|
|
9445
|
+
source: {
|
|
9446
|
+
producer: string;
|
|
9447
|
+
version: string;
|
|
9448
|
+
};
|
|
9449
|
+
})[] | null;
|
|
9378
9450
|
included: boolean;
|
|
9379
9451
|
treatmentUnresolved: boolean;
|
|
9380
9452
|
matchReason: string;
|
|
@@ -9465,6 +9537,33 @@ export interface components {
|
|
|
9465
9537
|
coveragePercent: number;
|
|
9466
9538
|
/** @enum {string} */
|
|
9467
9539
|
scanOutcome: "complete" | "budget_exhausted" | "file_ceiling" | "stopped";
|
|
9540
|
+
enumerationCompleteness: ("complete" | "local_budget_truncated" | "provider_incomplete" | "both") | null;
|
|
9541
|
+
rootCoverage: {
|
|
9542
|
+
/** @enum {string} */
|
|
9543
|
+
rootKind: "user" | "shared_drive" | "domain";
|
|
9544
|
+
rootId: string | null;
|
|
9545
|
+
/** @enum {string} */
|
|
9546
|
+
status: "complete" | "local_budget_truncated" | "provider_incomplete" | "both";
|
|
9547
|
+
enumeratedCount: number;
|
|
9548
|
+
observedOldestModifiedTime: string | null;
|
|
9549
|
+
observedNewestModifiedTime: string | null;
|
|
9550
|
+
skippedRanges: {
|
|
9551
|
+
/** Format: date-time */
|
|
9552
|
+
from: string;
|
|
9553
|
+
/** Format: date-time */
|
|
9554
|
+
to: string;
|
|
9555
|
+
}[];
|
|
9556
|
+
}[] | null;
|
|
9557
|
+
stageCounters: {
|
|
9558
|
+
filesEnumerated: number;
|
|
9559
|
+
filesMetadataEligible: number;
|
|
9560
|
+
filesT0Scored: number;
|
|
9561
|
+
filesT1Attended: number;
|
|
9562
|
+
filesPeeked: number;
|
|
9563
|
+
filesClassified: number;
|
|
9564
|
+
filesShown: number;
|
|
9565
|
+
filesRetained: number;
|
|
9566
|
+
} | null;
|
|
9468
9567
|
} | null;
|
|
9469
9568
|
};
|
|
9470
9569
|
SourceSyncApproveResponse: {
|
|
@@ -17982,6 +18081,47 @@ export interface operations {
|
|
|
17982
18081
|
};
|
|
17983
18082
|
};
|
|
17984
18083
|
};
|
|
18084
|
+
getQualityDecisionDiagnosis: {
|
|
18085
|
+
parameters: {
|
|
18086
|
+
query?: never;
|
|
18087
|
+
header?: never;
|
|
18088
|
+
path: {
|
|
18089
|
+
id: string;
|
|
18090
|
+
};
|
|
18091
|
+
cookie?: never;
|
|
18092
|
+
};
|
|
18093
|
+
requestBody?: never;
|
|
18094
|
+
responses: {
|
|
18095
|
+
/** @description The decision, whether its diagnosis is stored, pending or not applicable, and the report once stored */
|
|
18096
|
+
200: {
|
|
18097
|
+
headers: {
|
|
18098
|
+
[name: string]: unknown;
|
|
18099
|
+
};
|
|
18100
|
+
content?: never;
|
|
18101
|
+
};
|
|
18102
|
+
/** @description Validation error */
|
|
18103
|
+
400: {
|
|
18104
|
+
headers: {
|
|
18105
|
+
[name: string]: unknown;
|
|
18106
|
+
};
|
|
18107
|
+
content?: never;
|
|
18108
|
+
};
|
|
18109
|
+
/** @description Not an internal admin, or missing internal.manage_quality */
|
|
18110
|
+
403: {
|
|
18111
|
+
headers: {
|
|
18112
|
+
[name: string]: unknown;
|
|
18113
|
+
};
|
|
18114
|
+
content?: never;
|
|
18115
|
+
};
|
|
18116
|
+
/** @description No such decision */
|
|
18117
|
+
404: {
|
|
18118
|
+
headers: {
|
|
18119
|
+
[name: string]: unknown;
|
|
18120
|
+
};
|
|
18121
|
+
content?: never;
|
|
18122
|
+
};
|
|
18123
|
+
};
|
|
18124
|
+
};
|
|
17985
18125
|
listCommentThreads: {
|
|
17986
18126
|
parameters: {
|
|
17987
18127
|
query: {
|
|
@@ -110,6 +110,7 @@ export const openApiRoutes = {
|
|
|
110
110
|
'/api/internal-admin/quality/candidates/{id}/accept': ['POST'],
|
|
111
111
|
'/api/internal-admin/quality/candidates/{id}/reject': ['POST'],
|
|
112
112
|
'/api/internal-admin/quality/changes/manual': ['POST'],
|
|
113
|
+
'/api/internal-admin/quality/decisions/{id}/diagnosis': ['GET'],
|
|
113
114
|
'/api/internal-admin/quality/escalations': ['GET'],
|
|
114
115
|
'/api/internal-admin/quality/escalations/{id}/resolve': ['POST'],
|
|
115
116
|
'/api/internal-admin/quality/graders': ['GET'],
|
package/src/proactive/README.md
CHANGED
|
@@ -134,6 +134,23 @@ the three from another.
|
|
|
134
134
|
broken, so it gets an unread inbox row (`proactive.org_structure_review_ready`)
|
|
135
135
|
and no chat or banner. It is triggered by the committed insert of a
|
|
136
136
|
`validated` proposal, and its occurrence key is the proposal id.
|
|
137
|
+
- **The two workflow settlement kinds are one `explained` and one `notice`**
|
|
138
|
+
(control ADR-CTRL-540 phase 2; control ADR `affordance-system`). Both are
|
|
139
|
+
triggered by a ROUTED workflow settlement — the committed `deferred → routed`
|
|
140
|
+
transition of a settlement nobody was watching, routed per reason through
|
|
141
|
+
`../workflows/deferred-route` — and both key on
|
|
142
|
+
`workflow-settled:{workflowId}:{executionEpoch}`. `workflow.needs_attention`
|
|
143
|
+
is `explained`: the `workflowNeedsAttention` chat is FACT-FREE (no counts —
|
|
144
|
+
the outcome's facts are stripped), carries the settled outcome as
|
|
145
|
+
`ProactiveChatMessage.outcome` so its grounded affordances render as real
|
|
146
|
+
controls, and has NO chips (a chip only sends a chat prompt, which is not
|
|
147
|
+
grounded). Its composer frames and never narrates: `text` is the backend's
|
|
148
|
+
settlement narration verbatim, and its per-type title states no digit and
|
|
149
|
+
no next step. The inbox row is born read. `workflow.finished` is a `notice`:
|
|
150
|
+
an unread inbox row and nothing else, because telling it needs counts that
|
|
151
|
+
must re-render under the reader's authority. Their rows are
|
|
152
|
+
`proactive.workflow_needs_attention` and `proactive.workflow_finished`.
|
|
153
|
+
Neither sets `emailKind` or `actionItemKind`.
|
|
137
154
|
- **The occurrence vocabulary is closed.** `ProactiveEventId` is branded so a
|
|
138
155
|
raw string cannot stand in for an occurrence; `ProactiveRecipientReason` is a
|
|
139
156
|
closed enum because the recipients table is the frozen audit record of who
|
|
@@ -172,9 +189,9 @@ the three from another.
|
|
|
172
189
|
| `bornRead` | Whether a class's `chat` or `inbox` row is born read |
|
|
173
190
|
| `ORG_SYSTEM_EVENT_TYPES` | Mirror of the backend `org_system_event_type` pgEnum — the banner ids |
|
|
174
191
|
| `OrgSystemEventType` | Union derived from the array |
|
|
175
|
-
| `PROACTIVE_CHAT_TEMPLATES` | Which prose composes a kind's pushed chat (
|
|
192
|
+
| `PROACTIVE_CHAT_TEMPLATES` | Which prose composes a kind's pushed chat (9, incl. `workflowNeedsAttention`) |
|
|
176
193
|
| `ProactiveChatTemplate` | Union derived from the array |
|
|
177
|
-
| `PROACTIVE_EVENT_KIND_IDS` | Closed kinds (
|
|
194
|
+
| `PROACTIVE_EVENT_KIND_IDS` | Closed kinds (org/brief/chat, `billing.*`, `sourceSync.*`, `workflow.*` …) |
|
|
178
195
|
| `ProactiveEventKind` | Union derived from the array |
|
|
179
196
|
| `ProactiveEventId` | Branded id of ONE occurrence — a raw string cannot be passed as one |
|
|
180
197
|
| `PROACTIVE_RECIPIENT_REASONS` | Closed enum of WHY a recipient was addressed (the frozen audit record) |
|
|
@@ -192,7 +209,9 @@ the three from another.
|
|
|
192
209
|
| `BillingSuspendedFacts` | `orgName` — the prose needs nothing else, and may state nothing else |
|
|
193
210
|
| `BillingSubscriptionCanceledFacts` | `orgName` |
|
|
194
211
|
| `SourceSyncReviewReadyFacts` | `orgName` + `checkpoint`; no count, no document name, no source name |
|
|
195
|
-
| `
|
|
212
|
+
| `ExplainedWorkflowOutcomeData` | The settlement data part with an explained, fact-free outcome |
|
|
213
|
+
| `WorkflowNeedsAttentionFacts` | `narration` (backend settlement copy) + the fact-free `outcome` data part |
|
|
214
|
+
| `ProactiveChatMessage` | `title`, `text`, 0..4 `replies` chips, and an optional settled `outcome` |
|
|
196
215
|
| `ProactiveChatComposer` | The composer port — `template` plus an arrow-property `compose(facts)` |
|
|
197
216
|
| `PROACTIVE_CHAT_MAX_REPLIES` | At most this many chips (4); more is a menu, not a chip row |
|
|
198
217
|
| `PROACTIVE_CHAT_COMPOSERS` | The total template → composer registry (see `templates/README.md`) |
|
|
@@ -203,5 +222,8 @@ Type-only imports of `UserNotificationKind` (`../user-notifications`),
|
|
|
203
222
|
`NotificationKind` (`../notifications`) and `ActionItemKind`
|
|
204
223
|
(`../action-items`) — the three vocabularies this one composes — and of
|
|
205
224
|
`SuggestedReply` (`../message-parts`), the chip shape a composer's `replies`
|
|
206
|
-
reuse rather than redeclare.
|
|
225
|
+
reuse rather than redeclare. Type-only imports of `WorkflowOutcomeData`
|
|
226
|
+
(`../message-parts`), `ExplainedWorkflowOutcome` (`../workflows`) and
|
|
227
|
+
`WorkflowType` for the workflow settlement chat; the dependency runs
|
|
228
|
+
proactive → workflows, never the reverse. No `zod`, no runtime imports; pure vocabulary
|
|
207
229
|
data and pure helpers.
|
|
@@ -51,6 +51,16 @@ cannot.
|
|
|
51
51
|
must not be told to update one), past-due separates the seat subscription from
|
|
52
52
|
the AI balance, and suspension and cancellation each say outright that nothing
|
|
53
53
|
was deleted.
|
|
54
|
+
- `workflow-attention.test.ts` — the two workflow settlement kinds (control
|
|
55
|
+
ADR-CTRL-540 phase 2): `workflow.needs_attention` is explained (a chat and a
|
|
56
|
+
born-read inbox row, never a banner) and `workflow.finished` is a notice (an
|
|
57
|
+
unread inbox row only); the `workflowNeedsAttention` chat carries the
|
|
58
|
+
fact-free outcome (schema-valid, `facts: null`) and no chips, frames the
|
|
59
|
+
backend narration verbatim, has a title per workflow type with no digit and
|
|
60
|
+
no next-step wording; and both inbox rows supersede per workflow type.
|
|
61
|
+
- `workflow-attention-fixtures.ts` — explained, fact-free outcomes for those
|
|
62
|
+
tests and the registry-wide fixtures. A COMPILED module, so each fixture is
|
|
63
|
+
typechecked against `ExplainedWorkflowOutcome`.
|
|
54
64
|
|
|
55
65
|
## Invariants
|
|
56
66
|
|
|
@@ -6,6 +6,10 @@ import type { OrgSharedTransitionTrigger } from "../../org/shared-transition";
|
|
|
6
6
|
import { PROACTIVE_CHAT_TEMPLATES } from "../surfaces";
|
|
7
7
|
import type { ProactiveChatTemplate } from "../surfaces";
|
|
8
8
|
import { PROACTIVE_CHAT_COMPOSERS } from "../templates/index";
|
|
9
|
+
import {
|
|
10
|
+
DRIVE_AUTH_NARRATION,
|
|
11
|
+
DRIVE_AUTH_OUTCOME,
|
|
12
|
+
} from "./workflow-attention-fixtures";
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* One representative facts fixture per template. Total over
|
|
@@ -32,6 +36,10 @@ const FACTS: { readonly [T in ProactiveChatTemplate]: ProactiveChatFacts[T] } =
|
|
|
32
36
|
billingSuspended: { orgName: "Acme" },
|
|
33
37
|
billingSubscriptionCanceled: { orgName: "Acme" },
|
|
34
38
|
sourceSyncReviewReady: { orgName: "Acme", checkpoint: "candidates" },
|
|
39
|
+
workflowNeedsAttention: {
|
|
40
|
+
narration: DRIVE_AUTH_NARRATION,
|
|
41
|
+
outcome: { workflowId: "wf-1", outcome: DRIVE_AUTH_OUTCOME },
|
|
42
|
+
},
|
|
35
43
|
};
|
|
36
44
|
|
|
37
45
|
/**
|
|
@@ -37,6 +37,10 @@ import {
|
|
|
37
37
|
PLAN_FIXTURES_THE_COMPILER_REJECTS,
|
|
38
38
|
REGISTRY_FIXTURES_THE_COMPILER_REJECTS,
|
|
39
39
|
} from "./compile-fixtures";
|
|
40
|
+
import {
|
|
41
|
+
DRIVE_AUTH_NARRATION,
|
|
42
|
+
DRIVE_AUTH_OUTCOME,
|
|
43
|
+
} from "./workflow-attention-fixtures";
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* The registry, read as the union it satisfies rather than as its literal
|
|
@@ -103,6 +107,10 @@ const FRESH_FACTS: {
|
|
|
103
107
|
orgName: "Acme",
|
|
104
108
|
checkpoint: "synthesis" as const,
|
|
105
109
|
}),
|
|
110
|
+
workflowNeedsAttention: () => ({
|
|
111
|
+
narration: DRIVE_AUTH_NARRATION,
|
|
112
|
+
outcome: { workflowId: "wf-1", outcome: DRIVE_AUTH_OUTCOME },
|
|
113
|
+
}),
|
|
106
114
|
};
|
|
107
115
|
|
|
108
116
|
/**
|
|
@@ -259,6 +267,9 @@ describe("the kind registry", () => {
|
|
|
259
267
|
"sourceSync.review_ready": "proactive",
|
|
260
268
|
// The org structure row is rendered by `src/proactive/` as well.
|
|
261
269
|
"orgStructure.review_ready": "proactive",
|
|
270
|
+
// Both workflow settlement rows are rendered by `src/proactive/`.
|
|
271
|
+
"workflow.needs_attention": "proactive",
|
|
272
|
+
"workflow.finished": "proactive",
|
|
262
273
|
};
|
|
263
274
|
|
|
264
275
|
expect(Object.keys(EXPECTED_INBOX_DOMAIN).sort()).toEqual(
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explained, fact-free workflow outcomes for the `workflowNeedsAttention`
|
|
3
|
+
* template's tests. A COMPILED module (not `*.test.ts`), so every fixture is
|
|
4
|
+
* checked against `ExplainedWorkflowOutcome` — a reason that is not explained,
|
|
5
|
+
* or facts left in, fails the typecheck rather than passing silently.
|
|
6
|
+
*
|
|
7
|
+
* The narrations are fixture strings standing in for the backend's settlement
|
|
8
|
+
* copy; contracts never owns that wording.
|
|
9
|
+
*/
|
|
10
|
+
import type { ExplainedWorkflowOutcomeData } from "../facts";
|
|
11
|
+
import type { ExplainedWorkflowOutcome } from "../../workflows/deferred-route";
|
|
12
|
+
|
|
13
|
+
export const DRIVE_AUTH_NARRATION =
|
|
14
|
+
"The search of the connected drive stopped because Google Drive no longer accepts the connection's sign-in.";
|
|
15
|
+
|
|
16
|
+
export const DRIVE_AUTH_OUTCOME: ExplainedWorkflowOutcome = {
|
|
17
|
+
workflowType: "source-sync/discover",
|
|
18
|
+
executionEpoch: 0,
|
|
19
|
+
kind: "failed",
|
|
20
|
+
reason: "drive_auth",
|
|
21
|
+
facts: null,
|
|
22
|
+
disposition: { kind: "narrate" },
|
|
23
|
+
affordances: [{ id: "integrations.connectGoogleDrive" }],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const PAUSED_BILLING_OUTCOME: ExplainedWorkflowOutcome = {
|
|
27
|
+
workflowType: "source-sync/synthesize",
|
|
28
|
+
executionEpoch: 1,
|
|
29
|
+
kind: "failed",
|
|
30
|
+
reason: "paused_billing",
|
|
31
|
+
facts: null,
|
|
32
|
+
disposition: { kind: "narrate" },
|
|
33
|
+
affordances: [{ id: "billing.open" }],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const MODEL_FAILED_OUTCOME: ExplainedWorkflowOutcome = {
|
|
37
|
+
workflowType: "org-structure/reanalyze",
|
|
38
|
+
executionEpoch: 2,
|
|
39
|
+
kind: "failed",
|
|
40
|
+
reason: "model_failed",
|
|
41
|
+
facts: null,
|
|
42
|
+
disposition: { kind: "narrate" },
|
|
43
|
+
affordances: [{ id: "orgStructure.reanalyze" }],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** The settlement data part for an outcome, as the backend would hand it over. */
|
|
47
|
+
export const outcomeDataOf = (
|
|
48
|
+
outcome: ExplainedWorkflowOutcome,
|
|
49
|
+
): ExplainedWorkflowOutcomeData => ({ workflowId: "wf-1", outcome });
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two workflow settlement proactive kinds (control ADR-CTRL-540 phase 2;
|
|
3
|
+
* control ADR affordance-system): `workflow.needs_attention` is explained and
|
|
4
|
+
* its chat only FRAMES the backend's narration, `workflow.finished` is a notice.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import { CLASS_SURFACES, bornRead } from "../classes";
|
|
8
|
+
import type { ProactiveChatMessage } from "../composer";
|
|
9
|
+
import type { WorkflowNeedsAttentionFacts } from "../facts";
|
|
10
|
+
import { PROACTIVE_EVENT_KINDS } from "../registry";
|
|
11
|
+
import { PROACTIVE_CHAT_COMPOSERS } from "../templates/index";
|
|
12
|
+
import { notificationKeysOf } from "../../user-notifications/condition-keys";
|
|
13
|
+
import { NOTIFICATION_LIFECYCLE } from "../../user-notifications/lifecycle-registry";
|
|
14
|
+
import { WorkflowOutcomeSchema } from "../../workflows/outcome";
|
|
15
|
+
import { ANALYSIS_WORKFLOW_TYPES } from "../../workflows/types";
|
|
16
|
+
import type { ExplainedWorkflowOutcome } from "../../workflows/deferred-route";
|
|
17
|
+
import {
|
|
18
|
+
DRIVE_AUTH_NARRATION,
|
|
19
|
+
DRIVE_AUTH_OUTCOME,
|
|
20
|
+
MODEL_FAILED_OUTCOME,
|
|
21
|
+
PAUSED_BILLING_OUTCOME,
|
|
22
|
+
outcomeDataOf,
|
|
23
|
+
} from "./workflow-attention-fixtures";
|
|
24
|
+
|
|
25
|
+
// Mirrors the backend settlement-copy ban (backend tests/chat/workflow-outcome-copy.test.ts).
|
|
26
|
+
const CALL_TO_ACTION =
|
|
27
|
+
/\byou can\b|\btry\b|\bagain\b|\bsettings\b|\breconnect\b|\badd funds\b|\bclick\b|\bgo to\b|\bopen\b/i;
|
|
28
|
+
|
|
29
|
+
const compose = (facts: WorkflowNeedsAttentionFacts): ProactiveChatMessage =>
|
|
30
|
+
PROACTIVE_CHAT_COMPOSERS.workflowNeedsAttention.compose(facts);
|
|
31
|
+
|
|
32
|
+
const DRIVE_AUTH_FACTS: WorkflowNeedsAttentionFacts = {
|
|
33
|
+
narration: DRIVE_AUTH_NARRATION,
|
|
34
|
+
outcome: outcomeDataOf(DRIVE_AUTH_OUTCOME),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The drive-auth outcome re-typed as each workflow type, for title coverage. */
|
|
38
|
+
const outcomeAs = (
|
|
39
|
+
workflowType: (typeof ANALYSIS_WORKFLOW_TYPES)[number],
|
|
40
|
+
): ExplainedWorkflowOutcome =>
|
|
41
|
+
({ ...DRIVE_AUTH_OUTCOME, workflowType }) as ExplainedWorkflowOutcome;
|
|
42
|
+
|
|
43
|
+
describe("workflow settlement proactive kinds", () => {
|
|
44
|
+
it("workflow.needs_attention is explained: a chat and a born-read inbox row, never a banner", () => {
|
|
45
|
+
const plan = PROACTIVE_EVENT_KINDS["workflow.needs_attention"];
|
|
46
|
+
expect(plan).toEqual({
|
|
47
|
+
class: "explained",
|
|
48
|
+
chatTemplate: "workflowNeedsAttention",
|
|
49
|
+
inboxKind: "proactive.workflow_needs_attention",
|
|
50
|
+
});
|
|
51
|
+
const surfaces = CLASS_SURFACES[plan.class];
|
|
52
|
+
expect(surfaces.banner).toBe(false);
|
|
53
|
+
expect(surfaces.chat).toBe(true);
|
|
54
|
+
expect(bornRead(surfaces, "inbox")).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("workflow.finished is a notice: an unread inbox row, no banner and no chat", () => {
|
|
58
|
+
const plan = PROACTIVE_EVENT_KINDS["workflow.finished"];
|
|
59
|
+
expect(plan).toEqual({
|
|
60
|
+
class: "notice",
|
|
61
|
+
inboxKind: "proactive.workflow_finished",
|
|
62
|
+
});
|
|
63
|
+
const surfaces = CLASS_SURFACES[plan.class];
|
|
64
|
+
expect(surfaces.banner).toBe(false);
|
|
65
|
+
expect(surfaces.chat).toBe(false);
|
|
66
|
+
expect(bornRead(surfaces, "inbox")).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("the attention chat carries the fact-free outcome and no chips", () => {
|
|
70
|
+
const msg = compose(DRIVE_AUTH_FACTS);
|
|
71
|
+
expect(msg.replies).toEqual([]);
|
|
72
|
+
expect(msg.outcome).toEqual({
|
|
73
|
+
workflowId: "wf-1",
|
|
74
|
+
outcome: DRIVE_AUTH_OUTCOME,
|
|
75
|
+
});
|
|
76
|
+
expect(msg.outcome?.outcome.facts).toBeNull();
|
|
77
|
+
expect(WorkflowOutcomeSchema.safeParse(msg.outcome?.outcome).success).toBe(
|
|
78
|
+
true,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("the attention chat frames the backend narration verbatim", () => {
|
|
83
|
+
for (const outcome of [
|
|
84
|
+
DRIVE_AUTH_OUTCOME,
|
|
85
|
+
PAUSED_BILLING_OUTCOME,
|
|
86
|
+
MODEL_FAILED_OUTCOME,
|
|
87
|
+
]) {
|
|
88
|
+
const narration = `Narration for ${outcome.workflowType} ${outcome.reason}.`;
|
|
89
|
+
const msg = compose({ narration, outcome: outcomeDataOf(outcome) });
|
|
90
|
+
expect(msg.text, outcome.workflowType).toBe(narration);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("the attention chat has a title for every workflow type", () => {
|
|
95
|
+
for (const workflowType of ANALYSIS_WORKFLOW_TYPES) {
|
|
96
|
+
const msg = compose({
|
|
97
|
+
narration: DRIVE_AUTH_NARRATION,
|
|
98
|
+
outcome: outcomeDataOf(outcomeAs(workflowType)),
|
|
99
|
+
});
|
|
100
|
+
expect(typeof msg.title, workflowType).toBe("string");
|
|
101
|
+
expect(msg.title.trim().length, workflowType).toBeGreaterThan(0);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("the attention chat title states no digit and offers no next step", () => {
|
|
106
|
+
for (const workflowType of ANALYSIS_WORKFLOW_TYPES) {
|
|
107
|
+
const { title } = compose({
|
|
108
|
+
narration: DRIVE_AUTH_NARRATION,
|
|
109
|
+
outcome: outcomeDataOf(outcomeAs(workflowType)),
|
|
110
|
+
});
|
|
111
|
+
expect(title, workflowType).not.toMatch(/\d/);
|
|
112
|
+
expect(title, workflowType).not.toMatch(CALL_TO_ACTION);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("both workflow inbox rows supersede per workflow type", () => {
|
|
117
|
+
for (const kind of [
|
|
118
|
+
"proactive.workflow_needs_attention",
|
|
119
|
+
"proactive.workflow_finished",
|
|
120
|
+
] as const) {
|
|
121
|
+
const lifecycle = NOTIFICATION_LIFECYCLE[kind];
|
|
122
|
+
expect(lifecycle.condition, kind).toEqual({
|
|
123
|
+
scope: "fields",
|
|
124
|
+
fields: ["workflowType"],
|
|
125
|
+
});
|
|
126
|
+
expect(lifecycle.supersedes, kind).toBe(true);
|
|
127
|
+
expect(lifecycle.resolution, kind).toBe("never");
|
|
128
|
+
|
|
129
|
+
const first = notificationKeysOf(kind, {
|
|
130
|
+
workflowType: "source-sync/discover",
|
|
131
|
+
workflowId: "wf-1",
|
|
132
|
+
});
|
|
133
|
+
const second = notificationKeysOf(kind, {
|
|
134
|
+
workflowType: "source-sync/discover",
|
|
135
|
+
workflowId: "wf-2",
|
|
136
|
+
});
|
|
137
|
+
const other = notificationKeysOf(kind, {
|
|
138
|
+
workflowType: "org-structure/reanalyze",
|
|
139
|
+
workflowId: "wf-3",
|
|
140
|
+
});
|
|
141
|
+
expect(first.supersessionKey, kind).not.toBeNull();
|
|
142
|
+
expect(second.supersessionKey, kind).toBe(first.supersessionKey);
|
|
143
|
+
expect(other.supersessionKey, kind).not.toBe(first.supersessionKey);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import type { SuggestedReply } from "../message-parts/suggested-replies";
|
|
38
|
+
import type { WorkflowOutcomeData } from "../message-parts/workflow-outcome";
|
|
38
39
|
import type { ProactiveChatFacts } from "./facts";
|
|
39
40
|
import type { ProactiveChatTemplate } from "./surfaces";
|
|
40
41
|
|
|
@@ -65,6 +66,13 @@ export interface ProactiveChatMessage {
|
|
|
65
66
|
readonly text: string;
|
|
66
67
|
/** 0..`PROACTIVE_CHAT_MAX_REPLIES` chips. */
|
|
67
68
|
readonly replies: readonly SuggestedReply[];
|
|
69
|
+
/**
|
|
70
|
+
* The settled workflow a template reports, when it reports one. The chat
|
|
71
|
+
* writer persists it as a `data-workflow-outcome` part beside the prose, so
|
|
72
|
+
* the same renderer shows its affordances. Present only for a template whose
|
|
73
|
+
* facts carry an outcome; always fact-free here.
|
|
74
|
+
*/
|
|
75
|
+
readonly outcome?: WorkflowOutcomeData;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
// =============================================================================
|