@opencxh/domain 1.274.0 → 1.276.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/entities/activity/types.d.ts +5 -1
  2. package/dist/entities/ai-account/capabilities.d.ts +4 -0
  3. package/dist/entities/ai-account/index.d.ts +3 -0
  4. package/dist/entities/ai-account/modality.d.ts +24 -0
  5. package/dist/entities/ai-account/modality.test.d.ts +1 -0
  6. package/dist/entities/ai-account/routing.d.ts +71 -0
  7. package/dist/entities/ai-account/routing.test.d.ts +1 -0
  8. package/dist/entities/ai-account/system-one.d.ts +109 -0
  9. package/dist/entities/ai-account/system-one.test.d.ts +1 -0
  10. package/dist/entities/ai-account/types.d.ts +12 -5
  11. package/dist/entities/ai-account/vendors.d.ts +2 -7
  12. package/dist/entities/ai-message/types.d.ts +9 -0
  13. package/dist/entities/ai-settings/types.d.ts +9 -0
  14. package/dist/entities/approval/decide-each.d.ts +16 -0
  15. package/dist/entities/approval/decide-each.test.d.ts +1 -0
  16. package/dist/entities/approval/index.d.ts +1 -0
  17. package/dist/entities/eval/index.d.ts +2 -0
  18. package/dist/entities/eval/runner.d.ts +14 -0
  19. package/dist/entities/eval/runner.test.d.ts +1 -0
  20. package/dist/entities/eval/types.d.ts +46 -0
  21. package/dist/entities/inbox/types.d.ts +4 -4
  22. package/dist/entities/interaction/attention.d.ts +12 -13
  23. package/dist/entities/kb/index.d.ts +1 -0
  24. package/dist/entities/kb/keys.d.ts +2 -0
  25. package/dist/entities/playbook/actor.d.ts +1 -12
  26. package/dist/entities/playbook/approval.d.ts +14 -0
  27. package/dist/entities/playbook/approval.test.d.ts +1 -0
  28. package/dist/entities/playbook/autonomy.d.ts +25 -0
  29. package/dist/entities/playbook/autonomy.test.d.ts +1 -0
  30. package/dist/entities/playbook/index.d.ts +2 -0
  31. package/dist/entities/playbook/labels.d.ts +1 -3
  32. package/dist/entities/playbook/types.d.ts +30 -38
  33. package/dist/entities/reaction/types.d.ts +6 -1
  34. package/dist/entities/rule/bind.d.ts +23 -0
  35. package/dist/entities/rule/bind.test.d.ts +1 -0
  36. package/dist/entities/rule/cron.d.ts +40 -0
  37. package/dist/entities/rule/cron.test.d.ts +1 -0
  38. package/dist/entities/rule/eval.d.ts +31 -0
  39. package/dist/entities/rule/index.d.ts +4 -0
  40. package/dist/entities/rule/match.d.ts +8 -0
  41. package/dist/entities/rule/outcome.d.ts +44 -0
  42. package/dist/entities/rule/outcome.test.d.ts +1 -0
  43. package/dist/entities/rule/trigger-source.d.ts +4 -4
  44. package/dist/entities/rule/types.d.ts +91 -4
  45. package/dist/entities/scope/actor.d.ts +0 -5
  46. package/dist/entities/sla/types.d.ts +1 -1
  47. package/dist/entities/webhook/types.d.ts +7 -1
  48. package/dist/index.cjs +20 -20
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +2061 -1700
  51. package/dist/platform/ai-tools.d.ts +34 -15
  52. package/dist/platform/permission.d.ts +2 -2
  53. package/dist/platform/resource-source.d.ts +19 -0
  54. package/dist/platform/tool-verdict.d.ts +22 -9
  55. package/package.json +1 -1
@@ -281,7 +281,7 @@ export type AIActionProposedPayload = {
281
281
  runId: string;
282
282
  playbookId?: string;
283
283
  playbookName?: string;
284
- /** The held actions, e.g. { action: "tool:mcp__jira__create_issue", params }. */
284
+ /** The held actions, e.g. { action: "mcp__jira__create_issue", params }. */
285
285
  actions: {
286
286
  action: string;
287
287
  params?: Record<string, unknown>;
@@ -289,6 +289,10 @@ export type AIActionProposedPayload = {
289
289
  status: "pending" | "approved" | "rejected";
290
290
  /** Who the proposal was made on behalf of, so the card can show "on behalf of Finance". */
291
291
  actor?: ActingIdentity;
292
+ /** The approval request that decides it — the card decides through that, not through ai. */
293
+ approvalRequestId?: string;
294
+ /** Every request this proposal waits on, one per guarded act; the first is `approvalRequestId`. */
295
+ approvalRequestIds?: string[];
292
296
  };
293
297
  /** Playbook lifecycle marker in the interaction timeline (system-authored). */
294
298
  export type PlaybookLifecyclePayload = {
@@ -38,6 +38,10 @@ export declare const AI_CAPABILITIES: readonly [{
38
38
  readonly id: "embedding";
39
39
  readonly defaultFlag: "isDefaultEmbedding";
40
40
  readonly label: "Embeddings";
41
+ }, {
42
+ readonly id: "decision";
43
+ readonly defaultFlag: "isDefaultDecision";
44
+ readonly label: "Beslissingen";
41
45
  }];
42
46
  /** What an account can be the default for, and what a vendor can claim. One vocabulary. */
43
47
  export type AiCapability = (typeof AI_CAPABILITIES)[number]["id"];
@@ -1,3 +1,6 @@
1
1
  export * from './capabilities';
2
+ export * from './modality';
3
+ export * from './routing';
4
+ export * from './system-one';
2
5
  export * from './types';
3
6
  export * from './vendors';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * What one **model** can take in and give back.
3
+ *
4
+ * The question used to be asked per *vendor* (`aiVendorAcceptsAttachment`), and that is wrong in
5
+ * both directions: within one vendor models differ — a small fast model and its flagship do not
6
+ * read the same things — and it covered input only, so `tts` stayed an account flag with a form
7
+ * and no adapter behind it.
8
+ *
9
+ * The vocabulary is the source's, not ours (`architecture.input_modalities` on OpenRouter), and
10
+ * deliberately so: it is synced hourly and a translation table here would be a second place to
11
+ * maintain the same fact. That is why these are plain strings.
12
+ */
13
+ export interface ModelModality {
14
+ in: string[];
15
+ out: string[];
16
+ }
17
+ /**
18
+ * Does this model accept this kind — or is it simply not in the catalog?
19
+ *
20
+ * **Three states, on purpose.** `undefined` means "the catalog does not know this model", which a
21
+ * caller must be able to tell from a "no": the answer then falls back to what the vendor
22
+ * declares, rather than silently refusing every attachment on a self-hosted or brand-new model.
23
+ */
24
+ export declare function modelAcceptsAttachment(modality: ModelModality | undefined, kind: string): boolean | undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Which model answers this turn — decided per request instead of once per profile.
3
+ *
4
+ * Today nobody chooses: the model stands on the profile, or it is the account's default. Every
5
+ * question gets the same brain, whether it is *"mark this conversation resolved"* or *"write the
6
+ * quote letter"*. That is slow on the cheap questions and expensive on all of them.
7
+ *
8
+ * The routing rules are **rules**, not a model: the shape of a request is known without asking
9
+ * anyone. A hop to a classifier here would cost ~100 ms and a call to decide something that is
10
+ * free to decide.
11
+ */
12
+ export declare const MODEL_TIERS: readonly ["fast", "standard", "deep"];
13
+ export type ModelTier = (typeof MODEL_TIERS)[number];
14
+ /** Which account and model answer at one level. Both, because a tier can live on another vendor. */
15
+ export interface TierBinding {
16
+ accountId: string;
17
+ model: string;
18
+ }
19
+ export type ModelTiers = Partial<Record<ModelTier, TierBinding>>;
20
+ /**
21
+ * What this turn looks like, in the things that are knowable **before** the model is asked.
22
+ *
23
+ * Deliberately no context size, though the plan names it. The size is only known after the turn
24
+ * is assembled, and the assembly needs the provider, which needs the account, which is what this
25
+ * decides. The rule it would serve (a small classify goes to `fast`) is carried by
26
+ * {@link RoutingRequest.expectsSchema}, and that is the case it was written for.
27
+ */
28
+ export interface RoutingRequest {
29
+ /** `ask` reads, `draft` prepares, `agent` may act. Absent counts as `agent`. */
30
+ mode?: string;
31
+ /** How many tools the profile puts on the table. Zero is the cheap case. */
32
+ toolCount?: number;
33
+ hasAttachments?: boolean;
34
+ /** A structured answer against a schema: classify, extract, judge. */
35
+ expectsSchema?: boolean;
36
+ /** This turn repairs a failed one. Measured, not guessed — see the control-repair trace. */
37
+ repair?: boolean;
38
+ /** The ceiling the role allows. A cap, never a per-turn choice. */
39
+ maxTier?: ModelTier;
40
+ /** `soft` = a budget passed its warning threshold. `hard` never reaches here; it refuses. */
41
+ budgetPressure?: "none" | "soft";
42
+ }
43
+ export interface RoutingDecision {
44
+ tier: ModelTier;
45
+ /** Why, in the order the rules fired. Lands in the run so a bill can be read back. */
46
+ reasons: string[];
47
+ }
48
+ /**
49
+ * The tier for one request.
50
+ *
51
+ * Order matters and is the whole design: pick a base from the shape, escalate a repair, then
52
+ * **degrade under budget pressure rather than refuse**, and finally clamp to whatever ceiling the
53
+ * role set. Degrading last-but-one is deliberate — a cheaper answer is nearly always better than
54
+ * no answer, and today a full budget is a closed door.
55
+ */
56
+ export declare function resolveTier(request: RoutingRequest): RoutingDecision;
57
+ export interface Route extends RoutingDecision {
58
+ accountId: string;
59
+ model: string;
60
+ /** False when no tier was bound and the profile's own choice stands. */
61
+ routed: boolean;
62
+ }
63
+ /**
64
+ * The tier, resolved to an account and a model.
65
+ *
66
+ * **An unconfigured organisation must change nothing.** Tiers are opt-in, so an empty map means
67
+ * the profile's own account and model answer exactly as they did — which is also why the walk
68
+ * downward exists: a half-filled ladder (`fast` and `deep`, nothing between) should still route
69
+ * rather than fall back to the profile and quietly ignore what was configured.
70
+ */
71
+ export declare function resolveRoute(tiers: ModelTiers | undefined, request: RoutingRequest, fallback: TierBinding): Route;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The System One contract: ask a model a **question about a state**, get a number back.
3
+ *
4
+ * A different shape of model from every other one in this platform. A chat model is handed a
5
+ * prompt and writes text; this one is handed a state plus a set of typed questions and answers
6
+ * each of them as a probability distribution. It generates nothing — the docs say so outright —
7
+ * which is exactly why it is worth having: *"does this answer the question?"* is a yes/no with a
8
+ * degree, and a chat model asked that writes a paragraph about it.
9
+ *
10
+ * **Questions come as a set, and that is the contract rather than an optimisation.** Measured by
11
+ * the vendor: thirteen questions in one call against a 54k-character document were 12.2× cheaper
12
+ * and 10× faster than thirteen calls, and each question is scored on its own — batching adds no
13
+ * cross-talk. Anything that asks one question at a time here is paying twelve times over.
14
+ */
15
+ /** What a `true` and a `false` mean for this question. Optional; the instructions usually say it. */
16
+ export interface NoulCriteria {
17
+ true?: string;
18
+ false?: string;
19
+ }
20
+ export interface NoulQuestion {
21
+ type: "noul";
22
+ instructions: string;
23
+ criteria?: NoulCriteria;
24
+ }
25
+ /** Options as `{ name: description }`. A `null` description means "the name says it". */
26
+ export interface ChoiceQuestion {
27
+ type: "choice";
28
+ instructions: string;
29
+ criteria: Record<string, string | null>;
30
+ }
31
+ /** An ordered rubric, lowest first. */
32
+ export interface ScoreQuestion {
33
+ type: "score";
34
+ instructions: string;
35
+ criteria: string[];
36
+ }
37
+ export type SystemOneQuestion = NoulQuestion | ChoiceQuestion | ScoreQuestion;
38
+ /**
39
+ * P(yes), 0..1 — the answer and the certainty in one number.
40
+ *
41
+ * **No separate `confidence`, and that is deliberate on the vendor's side**: a yes/no has two
42
+ * outcomes, so one number describes the whole distribution. 0.5 is not "unsure about yes", it is
43
+ * "yes and no weighed the same".
44
+ */
45
+ export interface NoulAnswer {
46
+ type: "noul";
47
+ noul: number;
48
+ }
49
+ export interface ChoiceAnswer {
50
+ type: "choice";
51
+ choice: string;
52
+ confidence: number;
53
+ probabilities: Record<string, number>;
54
+ }
55
+ export interface ScoreAnswer {
56
+ type: "score";
57
+ score: number;
58
+ confidence: number;
59
+ legend?: Record<string, string>;
60
+ probabilities?: Record<string, number>;
61
+ }
62
+ export type SystemOneAnswer = NoulAnswer | ChoiceAnswer | ScoreAnswer;
63
+ export interface SystemOneRequest {
64
+ /** The thing being judged. A string, or a labelled object — the labels are read. */
65
+ state: unknown;
66
+ model?: string;
67
+ questions: Record<string, SystemOneQuestion>;
68
+ }
69
+ export interface SystemOneResponse {
70
+ model: string;
71
+ answers: Record<string, SystemOneAnswer>;
72
+ usage?: {
73
+ input_tokens?: number;
74
+ output_tokens?: number;
75
+ };
76
+ }
77
+ /** Vendor limits, so a request is refused here rather than as a 422 nobody reads. */
78
+ export declare const MAX_CHOICE_OPTIONS = 255;
79
+ export declare const MIN_SCORE_LEVELS = 2;
80
+ export declare const MAX_SCORE_LEVELS = 10;
81
+ /**
82
+ * What is wrong with this question set, or `null`.
83
+ *
84
+ * Checked before the call because the alternative is a 422 with a vendor's wording in a log, and
85
+ * because these are the mistakes a caller makes while writing a question rather than at runtime.
86
+ */
87
+ export declare function validateQuestions(questions: Record<string, SystemOneQuestion>): string | null;
88
+ export type DecisionBand = "act" | "check" | "refuse";
89
+ /**
90
+ * What to *do* with a number, in three bands.
91
+ *
92
+ * The vendor is explicit that the thresholds are domain-specific and that you should start
93
+ * conservatively — so they are parameters, not constants, and the default is deliberately timid.
94
+ * The three bands are the vendor's own architecture advice: act, act-but-confirm, do not act.
95
+ *
96
+ * **`check` is the honest middle and must not be collapsed.** Two bands force every uncertain
97
+ * answer into either a silent action or a dead end; the middle is where a human gets asked, which
98
+ * is the entire reason a calibrated number is worth more than a label.
99
+ */
100
+ export declare function decisionBand(value: number, act?: number, check?: number): DecisionBand;
101
+ /**
102
+ * Questions this model should not be asked.
103
+ *
104
+ * Straight from the vendor's own jaggedness page, and worth having in code rather than in a
105
+ * doc: counting, arithmetic, raw numeric values and **dates** are named failure modes — it
106
+ * "reads dates as text, not as ordered quantities". Every one of those has a deterministic
107
+ * answer in this codebase already, so asking a model is both worse and more expensive.
108
+ */
109
+ export declare const SYSTEM_ONE_NON_GOALS: readonly ["counting or arithmetic", "comparing dates or durations", "judging whether two numbers are close", "generating text"];
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
- export interface AiAccount {
1
+ import { AiDefaultFlags } from './capabilities';
2
+ interface AiAccountFields {
2
3
  id: string;
3
4
  organizationId: string;
4
5
  vendor: string;
@@ -9,8 +10,14 @@ export interface AiAccount {
9
10
  * is ignored.
10
11
  */
11
12
  baseUrl?: string;
12
- isDefaultLlm?: boolean;
13
- isDefaultStt?: boolean;
14
- isDefaultTts?: boolean;
15
- isDefaultEmbedding?: boolean;
16
13
  }
14
+ /**
15
+ * The default flags, **derived** from the capability catalog rather than listed again.
16
+ *
17
+ * They were written out by hand here, which is the drift the catalog exists to stop: adding
18
+ * `decision` gave every other place a fifth checkbox and this interface a fourth, so the form
19
+ * offered a flag the type did not have. One list, everything else follows.
20
+ */
21
+ export interface AiAccount extends AiAccountFields, Partial<AiDefaultFlags> {
22
+ }
23
+ export {};
@@ -1,14 +1,9 @@
1
1
  import { AiCapability } from './capabilities';
2
- /**
3
- * What a vendor can do. Was its own union of three; it is the same vocabulary the account
4
- * defaults and the usage rows use, so it now names {@link AiCapability} instead of repeating it.
5
- */
6
- export type AiVendorCapability = AiCapability;
7
2
  export interface AiVendor {
8
3
  id: string;
9
4
  label: string;
10
5
  /** What this vendor can do. Decides where it appears as an option in the UI. */
11
- capabilities: readonly AiVendorCapability[];
6
+ capabilities: readonly AiCapability[];
12
7
  /** Model a call runs on when it carries none itself. Absent for vendors without a chat adapter. */
13
8
  defaultModel?: string;
14
9
  /**
@@ -38,7 +33,7 @@ export declare function aiVendorLabel(vendorId: string): string;
38
33
  */
39
34
  export declare function aiVendorDefaultModel(vendorId: string, fallback?: string): string;
40
35
  /** Vendors offering this capability — for capability-filtered pickers. */
41
- export declare function aiVendorsWith(capability: AiVendorCapability): AiVendor[];
36
+ export declare function aiVendorsWith(capability: AiCapability): AiVendor[];
42
37
  /** True when the user has to fill in a host themselves (the vendor has no fixed one). */
43
38
  export declare function aiVendorNeedsBaseUrl(vendorId: string): boolean;
44
39
  /** Does this vendor accept this attachment kind? Text always works — that is prompt. */
@@ -39,6 +39,8 @@ export interface AIMessageOutput {
39
39
  * question and then having the same thread draft a reply is the normal flow.
40
40
  */
41
41
  export type AssistantMode = "ask" | "draft" | "agent";
42
+ /** Is this one of the three modes? For request bodies, which may carry anything. */
43
+ export declare function isAssistantMode(value: unknown): value is AssistantMode;
42
44
  export interface AIMessage {
43
45
  id: string;
44
46
  organizationId: string;
@@ -97,6 +99,13 @@ export interface AIMessage {
97
99
  toolsTotal?: number;
98
100
  /** Which mode this turn ran as. Absent = `"agent"`: every row written before modes existed. */
99
101
  mode?: AssistantMode;
102
+ /**
103
+ * Why this model answered — one line per routing rule that fired.
104
+ *
105
+ * Absent when nothing was routed, which is every organisation that bound no tiers. A reason for
106
+ * a choice nobody made would read as a decision.
107
+ */
108
+ routing?: string[];
100
109
  }
101
110
  export type AIMessageFeedback = "up" | "down";
102
111
  /**
@@ -1,3 +1,4 @@
1
+ import { ModelTiers } from '../ai-account/routing';
1
2
  /**
2
3
  * What an organisation configures for the AI app as a whole — not per profile, not per user.
3
4
  *
@@ -5,6 +6,14 @@
5
6
  * setting nobody can find.
6
7
  */
7
8
  export interface AiOrgSettings {
9
+ /**
10
+ * Which account and model answer at `fast`, `standard` and `deep`.
11
+ *
12
+ * **Opt-in and empty by default.** An organisation that configures nothing keeps exactly what
13
+ * it has: the profile's own model answers every turn. Only once a tier is bound does the router
14
+ * (`resolveRoute`) get anything to choose between.
15
+ */
16
+ tiers?: ModelTiers;
8
17
  /**
9
18
  * May the AI app listen in on conversations (speech to text)?
10
19
  *
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Give one verdict on every request of a proposal that this person may decide.
3
+ *
4
+ * A park asks one request per guarded act, each with its own approvers; the person pressing the
5
+ * button signs the ones that are theirs. A request that is not theirs (403) or already settled
6
+ * (400) is counted as `waiting` rather than failing the rest — anything else is a real error.
7
+ */
8
+ export declare function decideEach(requestIds: readonly string[], decideOne: (requestId: string) => Promise<unknown>): Promise<{
9
+ decided: number;
10
+ waiting: number;
11
+ }>;
12
+ /** The requests a proposal waits on, from the list or the single id older rows carry. */
13
+ export declare function proposalRequestIds(source: {
14
+ approvalRequestIds?: readonly string[] | null;
15
+ approvalRequestId?: string | null;
16
+ }): string[];
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,3 @@
1
+ export * from './decide-each';
1
2
  export * from './policy';
2
3
  export * from './types';
@@ -0,0 +1,2 @@
1
+ export * from './runner';
2
+ export * from './types';
@@ -0,0 +1,14 @@
1
+ import { EvalCase, EvalCaseResult, EvalRun } from './types';
2
+ /**
3
+ * Run a suite. The judge is injected, which is the whole reason this is one function.
4
+ *
5
+ * A deterministic suite (rule correctness, a classification against a known answer) judges by
6
+ * comparison and costs nothing; a judged suite asks a model. Both are the same loop, and keeping
7
+ * that loop in one place is what makes "put this in CI" a decision about the judge rather than a
8
+ * second implementation.
9
+ *
10
+ * **Sequential, not parallel.** A judged suite is a batch of model calls, and firing forty at
11
+ * once is how a rate limit turns a measurement into a red build. A deterministic judge is fast
12
+ * enough that the difference does not register.
13
+ */
14
+ export declare function runEvalSuite<I, E>(suite: string, target: string, cases: readonly EvalCase<I, E>[], judge: (testCase: EvalCase<I, E>) => Promise<EvalCaseResult> | EvalCaseResult, now?: number): Promise<EvalRun>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Measuring whether this platform is any good at the things it decides.
3
+ *
4
+ * Today that exists once, as a test file inside the playbook folder: a handful of cases, opt-in,
5
+ * never in CI. That is not small-but-good, it is the wrong shape — it measures one kind of thing
6
+ * (a playbook prompt) while seven things need exactly the same machinery: prompt quality, the
7
+ * accuracy of a classification, **rule correctness**, retrieval, tool selection, whether an agent
8
+ * turn reached its control move, and live answer quality.
9
+ *
10
+ * Two design rules live in the shape itself. A case carries either an `expected` value **or** a
11
+ * `rubric`, because those are the two ways anything here can be judged: exactly, or by opinion —
12
+ * and an opinion needs a judge, which costs money and has to be calibrated. And a case is
13
+ * something you can *harvest*: every correction in the feedback loop is by definition a labelled
14
+ * case, while a golden set written by hand is out of date within a quarter.
15
+ */
16
+ export interface EvalCase<Input = unknown, Expected = unknown> {
17
+ id: string;
18
+ /** What kind of thing is judged: `rule`, `classify`, `retrieval`, `tool-selection`, … */
19
+ subject: string;
20
+ input: Input;
21
+ /** The exact answer, when there is one. Mutually exclusive with {@link EvalCase.rubric}. */
22
+ expected?: Expected;
23
+ /** What "good" means, for a case only a judge can settle. */
24
+ rubric?: string;
25
+ tags?: string[];
26
+ }
27
+ export interface EvalCaseResult {
28
+ caseId: string;
29
+ ok: boolean;
30
+ /** What actually happened, in the case's own terms. Present on a failure; optional otherwise. */
31
+ got?: unknown;
32
+ /** Why it failed, in a sentence a person can act on. */
33
+ note?: string;
34
+ }
35
+ export interface EvalRun {
36
+ suite: string;
37
+ /** What was measured: a model id, a rule set version, a prompt. */
38
+ target: string;
39
+ at: number;
40
+ /** Passed over total, 0..1. `1` on an empty suite — nothing failed. */
41
+ score: number;
42
+ perCase: EvalCaseResult[];
43
+ }
44
+ export declare function summariseEval(suite: string, target: string, perCase: readonly EvalCaseResult[], at?: number): EvalRun;
45
+ /** The failures, as lines. What a CI log should print and nothing more. */
46
+ export declare function failureLines(run: EvalRun): string[];
@@ -11,10 +11,10 @@ export interface Inbox {
11
11
  /**
12
12
  * Hours a first reply may take in this inbox before a conversation counts as at risk.
13
13
  *
14
- * Absent = the ladder per priority ({@link SLA_HOURS_BY_PRIORITY}), which is what every
15
- * organisation silently ran on: the onboarding step promised a norm per inbox, ticked itself
16
- * off on `ticketEnabled`, and nothing anywhere stored an agreed number. So the Focus badge and
17
- * Assist' SLA nudge were a hardcoded opinion.
14
+ * **Absent = no service target at all**, not a default. A ladder per priority used to stand in
15
+ * (1/4/8/24) and that was a hardcoded opinion: every inbox, and every conversation outside one,
16
+ * silently carried a promise nobody made. Deliberately not being in an SLA — personal mail —
17
+ * is the common case, so it is the one that needs no configuration.
18
18
  *
19
19
  * One number and not four (one per priority): an inbox usually *is* the agreement ("support:
20
20
  * within 4 hours"), and four fields per inbox is four things to keep consistent while the
@@ -1,4 +1,4 @@
1
- import { AttentionCtx, ParkMark, Priority, Scored } from '../../platform/attention';
1
+ import { AttentionCtx, ParkMark, Scored } from '../../platform/attention';
2
2
  import { Interaction } from './types';
3
3
  /** The only statuses that can ask for attention; `closed` and `snoozed` never compete. */
4
4
  export declare const ATTENTION_STATUSES: readonly ["open"];
@@ -15,11 +15,6 @@ export declare const ATTENTION_STATUSES: readonly ["open"];
15
15
  * larger than any positive contribution, so bulk always ranks below every real conversation.
16
16
  */
17
17
  export declare const BULK_PENALTY = 1000;
18
- /**
19
- * Hours a first response is "allowed" per priority before an open interaction counts as
20
- * SLA-at-risk. Also consumed by the Mijn dag KPI and the wait heuristic.
21
- */
22
- export declare const SLA_HOURS_BY_PRIORITY: Record<Priority, number>;
23
18
  /**
24
19
  * How long you wait for a reply before the conversation asks for attention again. Without this
25
20
  * bound a thread the customer never replied to would stay out of sight forever.
@@ -48,16 +43,17 @@ export declare function isReminderActive(reminder: ParkMark | undefined, i: Inte
48
43
  /** Whole hours an open interaction has been waiting (never negative). */
49
44
  export declare function waitHoursOf(i: Interaction, now: number): number;
50
45
  /**
51
- * The first-reply budget for this conversation, in hours.
46
+ * The first-reply budget for this conversation in hours, or `undefined` when nobody agreed one.
52
47
  *
53
- * The inbox's own norm wins when it has one; otherwise the ladder per priority. One helper
54
- * rather than the lookup at each call site, because three readers ask this question (the Focus
55
- * badge, "Mijn dag", and Assist' nudge tag) and a fourth answer would be a fourth opinion.
48
+ * **There is deliberately no fallback.** A ladder per priority used to fill this in (1/4/8/24),
49
+ * which invented an agreement nobody made: a personal mailbox got an eight-hour promise for the
50
+ * sole reason that its priority was unset. "No service target" is a valid state — most mail is
51
+ * in it — and it has to look different from a target someone actually configured.
56
52
  *
57
53
  * A non-positive stored value is treated as absent: "0 hours" is not an agreement, it is an
58
54
  * empty field that made it through a form.
59
55
  */
60
- export declare function slaHoursFor(i: Interaction, slaByInbox?: Record<string, number>): number;
56
+ export declare function slaHoursFor(i: Interaction, slaByInbox?: Record<string, number>): number | undefined;
61
57
  /**
62
58
  * Index inboxes the way {@link slaHoursFor} wants them: only the ones that named a norm.
63
59
  *
@@ -71,8 +67,11 @@ export declare function slaHoursByInbox(inboxes: readonly {
71
67
  /**
72
68
  * True when an open interaction has waited past its first-reply budget.
73
69
  *
74
- * `slaByInbox` maps inbox id -> agreed hours; a caller that has no inboxes at hand may leave it
75
- * out and gets the priority ladder, which is exactly the old behaviour.
70
+ * No budget, no risk. A conversation in an inbox that named no norm cannot be late, because
71
+ * there is nothing to be late for — so the answer is `false`, not "unknown". The one reader is a
72
+ * badge, and a badge that fires on an agreement that does not exist is the bug this removed.
73
+ *
74
+ * `slaByInbox` maps inbox id -> agreed hours; leave it out and nothing is ever at risk.
76
75
  */
77
76
  export declare function isSlaAtRisk(i: Interaction, now: number, slaByInbox?: Record<string, number>): boolean;
78
77
  /**
@@ -1,4 +1,5 @@
1
1
  export * from './help-center';
2
+ export * from './keys';
2
3
  export * from './locales';
3
4
  export * from './tree';
4
5
  export * from './types';
@@ -0,0 +1,2 @@
1
+ /** The scope kind of one article: `article:<id>`. One definition for the claim and the owner. */
2
+ export declare const ARTICLE_SCOPE_KIND = "article";
@@ -1,20 +1,9 @@
1
1
  import { ActingIdentity } from '../../platform/identity';
2
- import { toActingIdentity } from '../scope/actor';
3
2
  import { OwnerScope } from '../scope/types';
4
- /**
5
- * `ownerScope` **is** a playbook's actor: there is no separate `runAs` field. Visibility, working
6
- * area and attribution therefore all follow from the same field.
7
- *
8
- * On the storage side the personal variant is called `personal` (as with every other entity), on
9
- * the execution side `user` (as with every other acting identity). This function is that
10
- * translation, and the only place it belongs.
11
- */
12
- export declare const playbookActor: typeof toActingIdentity;
13
3
  /**
14
4
  * Who this run acts for.
15
5
  *
16
- * **Two different questions, and the whole reason this function exists next to
17
- * {@link playbookActor}:**
6
+ * **Two different questions**, which is why this is not just `toActingIdentity(ownerScope)`:
18
7
  *
19
8
  * - *who acts* — the agent, when one is designated;
20
9
  * - *who may manage this* — `ownerScope`, and that stays with humans.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A parked run's address in the approval layer: `playbook-run:<runId>~<step>`.
3
+ *
4
+ * The park is part of the key on purpose. The approval layer de-duplicates on `(scopeKey, act)`
5
+ * and keeps one approved row per key, so a flow that parks, resumes and parks again on a later
6
+ * step needs a key per park — while a retry of the *same* park must find its open request.
7
+ * `~` because the key travels in paths too, where `/` and `.` already mean something.
8
+ */
9
+ export declare const PLAYBOOK_RUN_SCOPE_KIND = "playbook-run";
10
+ /** The one act a parked run asks for: "carry out what was proposed". */
11
+ export declare const PLAYBOOK_RUN_APPROVAL_ACT = "run";
12
+ export declare function runApprovalKey(runId: string, step?: number): string;
13
+ /** The run behind an approval key's ref (`<runId>~<step>`), or the ref itself without a step. */
14
+ export declare function runIdOfApprovalRef(ref: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { Autonomy } from './types';
2
+ /** Is this one of the three settings? For request bodies, which may carry anything. */
3
+ export declare function isAutonomy(value: unknown): value is Autonomy;
4
+ /**
5
+ * How much a run may do on its own, decided **once** at its start and frozen on the run. The
6
+ * playbook's setting wins where there is one (that is where the organisation configured it), then
7
+ * the agent's, then `suggest`.
8
+ *
9
+ * `demoted`: several autonomous playbooks would act outward on the same subject at once. They
10
+ * keep doing internal work themselves; a human decides which one goes out.
11
+ */
12
+ export declare function resolveAutonomy(input: {
13
+ playbook?: {
14
+ autonomy?: Autonomy;
15
+ } | null;
16
+ agent?: {
17
+ autonomy?: Autonomy;
18
+ } | null;
19
+ demoted?: boolean;
20
+ }): Autonomy;
21
+ /**
22
+ * A step's own setting may make it more careful than its run, never less: the run's value is
23
+ * where a demotion and the agent's mandate already landed.
24
+ */
25
+ export declare function narrowAutonomy(run: Autonomy | undefined, step: Autonomy | undefined): Autonomy;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,7 @@
1
1
  export * from './actor';
2
+ export * from './approval';
2
3
  export * from './assignment';
4
+ export * from './autonomy';
3
5
  export * from './labels';
4
6
  export * from './trigger-vars';
5
7
  export * from './types';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Readable name of an action/tool for a human: `tool:communication__update_interaction` becomes
2
+ * Readable name of an action/tool for a human: `communication__update_interaction` becomes
3
3
  * `communication · update interaction`.
4
4
  *
5
5
  * Shared, because the proposal card in comms already had this helper while the run timeline in the
@@ -7,8 +7,6 @@
7
7
  * `voorstel: tool:mcp__jira__create_issue`.
8
8
  */
9
9
  export declare function humanizeAction(action: string): string;
10
- /** The namespaced tool id without the step's `tool:` prefix. */
11
- export declare function toolName(action: string): string;
12
10
  /**
13
11
  * The tool's own name, without the namespace that says who owns it:
14
12
  * `work__create_work_item` and `mcp__jira__create_issue` become `create_work_item` and