@opengeni/core 0.28.1 → 2.1.0-canary.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/dist/access/index.d.ts +37 -0
- package/dist/application/new-session-drafts.d.ts +8 -1
- package/dist/application/session-commands.d.ts +18 -11
- package/dist/application/session-tenancy.d.ts +18 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
- package/dist/chunk-H7X52RI2.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
- package/dist/chunk-VCZFFEPB.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
- package/dist/chunk-ZYCNLYGH.js.map +1 -0
- package/dist/dependencies.d.ts +12 -1
- package/dist/domain/automations.d.ts +51 -0
- package/dist/domain/company-brain-governed-writes.d.ts +70 -0
- package/dist/domain/company-profile-agent-admin.d.ts +24 -0
- package/dist/domain/connection-authority.d.ts +97 -0
- package/dist/domain/connection-authority.js +169 -0
- package/dist/domain/connection-authority.js.map +1 -0
- package/dist/domain/editable-artifacts/service.d.ts +1 -2
- package/dist/domain/environments.d.ts +1 -1
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/governed-learning-activation.d.ts +16 -0
- package/dist/domain/governed-learning-evaluator.d.ts +6 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/memory-slack-delivery.d.ts +1 -4
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/packs.d.ts +5 -1
- package/dist/domain/personal-connection-delegations.d.ts +44 -2
- package/dist/domain/pr-review.d.ts +149 -0
- package/dist/domain/remember.d.ts +69 -0
- package/dist/domain/resources.d.ts +23 -2
- package/dist/domain/scheduled-tasks.d.ts +43 -2
- package/dist/domain/session-tool-policy.d.ts +1 -1
- package/dist/domain/sessions.d.ts +43 -9
- package/dist/editable-artifact-live/application.d.ts +10 -2
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +0 -1
- package/dist/editable-artifact-live/ticket.d.ts +0 -1
- package/dist/editable-artifact-live/types.d.ts +5 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +2 -4
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4973 -1243
- package/dist/index.js.map +1 -1
- package/dist/rigs/index.d.ts +22 -5
- package/dist/sandbox/fleet.d.ts +34 -6
- package/dist/sandbox/routing.d.ts +15 -1
- package/dist/sandbox/runtime-settings.d.ts +39 -0
- package/package.json +16 -11
- package/src/access/index.ts +151 -31
- package/src/application/new-session-drafts.ts +32 -5
- package/src/application/session-commands.ts +305 -71
- package/src/application/session-tenancy.ts +321 -0
- package/src/application/user-resource-grants.ts +140 -0
- package/src/billing/limits.ts +25 -15
- package/src/dependencies.ts +12 -1
- package/src/domain/automations.ts +196 -0
- package/src/domain/capabilities.ts +7 -1
- package/src/domain/company-brain-governed-writes.ts +330 -0
- package/src/domain/company-profile-agent-admin.ts +55 -0
- package/src/domain/connection-authority.ts +329 -0
- package/src/domain/editable-artifacts/agent-application.ts +17 -9
- package/src/domain/editable-artifacts/service.ts +12 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +16 -3
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/governed-learning-activation.ts +44 -0
- package/src/domain/governed-learning-evaluator.ts +49 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights.ts +9 -0
- package/src/domain/memory-slack-delivery.ts +0 -30
- package/src/domain/organization-membership-lifecycle.ts +9 -0
- package/src/domain/packs.ts +106 -3
- package/src/domain/personal-connection-delegations.ts +597 -42
- package/src/domain/pr-review.ts +671 -0
- package/src/domain/remember.ts +549 -0
- package/src/domain/resources.ts +144 -6
- package/src/domain/scheduled-tasks.ts +504 -77
- package/src/domain/session-tool-policy.ts +12 -9
- package/src/domain/sessions.ts +629 -176
- package/src/editable-artifact-live/application.ts +21 -3
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +13 -7
- package/src/editable-artifact-live/ticket.ts +1 -5
- package/src/editable-artifact-live/types.ts +6 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +13 -1
- package/src/rigs/index.ts +165 -48
- package/src/sandbox/fleet.ts +376 -30
- package/src/sandbox/routing.ts +91 -29
- package/src/sandbox/runtime-settings.ts +274 -0
- package/src/session-authorization.ts +17 -49
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/src/domain/durable-learning-slack-publication.ts +0 -202
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CompanyBrainGovernedWriteAttempt,
|
|
3
|
+
REMEMBER_HUMAN_INPUT_SAVE_OPTION,
|
|
4
|
+
REMEMBER_HUMAN_INPUT_SKIP_OPTION,
|
|
5
|
+
TASK_NOTE_MAX_LIFETIME_DAYS,
|
|
6
|
+
RememberConfirmReceipt,
|
|
7
|
+
RememberConfirmRequest,
|
|
8
|
+
RememberReceipt,
|
|
9
|
+
RememberRequest,
|
|
10
|
+
rememberHumanInputQuestionId,
|
|
11
|
+
type CompanyBrainGovernedWriteAttempt as Attempt,
|
|
12
|
+
type CompanyBrainGovernedWriteRequest,
|
|
13
|
+
type RememberLane,
|
|
14
|
+
type CompanyBrainLearningPolicyRouteReceipt,
|
|
15
|
+
type GovernedLearningActivationReceipt,
|
|
16
|
+
type RememberConfirmReceipt as RememberConfirmReceiptType,
|
|
17
|
+
type RememberReceipt as RememberReceiptType,
|
|
18
|
+
} from "@opengeni/contracts";
|
|
19
|
+
import {
|
|
20
|
+
type Database,
|
|
21
|
+
INSTRUCTION_POLICY_STALE_BASELINE_DIAGNOSTIC,
|
|
22
|
+
WorkspaceInstructionPolicyOnboardingProposalStaleError,
|
|
23
|
+
activateHumanConfirmedLearningDecision,
|
|
24
|
+
archiveTaskNote,
|
|
25
|
+
confirmRememberKnowledgeClaim,
|
|
26
|
+
createTaskNote,
|
|
27
|
+
getWorkspaceInstructionPolicyBaseline,
|
|
28
|
+
getWorkspaceKnowledgeChangeProposalSummary,
|
|
29
|
+
getWorkspaceKnowledgeClaimInitiatingHuman,
|
|
30
|
+
nestedPostgresSqlState,
|
|
31
|
+
rebaselineWorkspaceInstructionPolicyKnowledgeProposal,
|
|
32
|
+
} from "@opengeni/db";
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
import {
|
|
35
|
+
createCompanyBrainLearningPolicyRouter,
|
|
36
|
+
derivedGovernedLearningOperationId,
|
|
37
|
+
} from "./company-brain-governed-writes";
|
|
38
|
+
import { publishGovernedLearningEventToSlack } from "./governed-learning-slack-publication";
|
|
39
|
+
|
|
40
|
+
// A confirmation may need to lose a race with a concurrent activation more
|
|
41
|
+
// than once before it lands, but it must not retry forever.
|
|
42
|
+
const REBASELINE_ATTEMPTS = 3;
|
|
43
|
+
|
|
44
|
+
export class RememberError extends Error {
|
|
45
|
+
readonly name = "RememberError";
|
|
46
|
+
constructor(
|
|
47
|
+
readonly code:
|
|
48
|
+
| "proposal_unavailable"
|
|
49
|
+
| "proposal_not_confirmable"
|
|
50
|
+
| "human_confirmation_unavailable"
|
|
51
|
+
| "baseline_stale",
|
|
52
|
+
message: string,
|
|
53
|
+
) {
|
|
54
|
+
super(message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The database layer collapses several SQLSTATEs into one typed conflict, so
|
|
59
|
+
// the exact diagnostic lives on the preserved `cause` chain rather than the
|
|
60
|
+
// outermost message.
|
|
61
|
+
function mentionsStaleInstructionBaseline(error: unknown): boolean {
|
|
62
|
+
for (let current = error, depth = 0; current instanceof Error && depth < 8; depth += 1) {
|
|
63
|
+
if (current.message.includes(INSTRUCTION_POLICY_STALE_BASELINE_DIAGNOSTIC)) return true;
|
|
64
|
+
current = current.cause;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A moved instruction-policy head is an ordinary, recoverable race: the agent
|
|
71
|
+
* read a baseline, someone activated a policy, and the compare-and-set caught
|
|
72
|
+
* it. It surfaced as an untyped Error (propose side) or a raw SQLSTATE 40001
|
|
73
|
+
* (confirm side) with nothing telling the caller what to do about it. Convert
|
|
74
|
+
* both into one typed, actionable failure and leave every other error exactly
|
|
75
|
+
* as it was.
|
|
76
|
+
*/
|
|
77
|
+
function asRememberFailure(error: unknown): unknown {
|
|
78
|
+
if (error instanceof WorkspaceInstructionPolicyOnboardingProposalStaleError) {
|
|
79
|
+
return new RememberError(
|
|
80
|
+
"baseline_stale",
|
|
81
|
+
"The workspace instruction policy changed while this rule was being prepared. " +
|
|
82
|
+
"Call remember again to rebuild it against the current policy.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (nestedPostgresSqlState(error) === "40001" && mentionsStaleInstructionBaseline(error)) {
|
|
86
|
+
return new RememberError(
|
|
87
|
+
"baseline_stale",
|
|
88
|
+
"The workspace instruction policy changed after this rule was proposed, so the " +
|
|
89
|
+
"confirmation no longer applies to the current policy. Call remember again to " +
|
|
90
|
+
"rebuild it against the current policy and ask for confirmation once more.",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type RememberRouterOptions = {
|
|
97
|
+
db: Database;
|
|
98
|
+
learningRouter?: Pick<ReturnType<typeof createCompanyBrainLearningPolicyRouter>, "write">;
|
|
99
|
+
createNote?: typeof createTaskNote;
|
|
100
|
+
archiveNote?: typeof archiveTaskNote;
|
|
101
|
+
rebaselineProposal?: typeof rebaselineWorkspaceInstructionPolicyKnowledgeProposal;
|
|
102
|
+
activateHumanConfirmed?: typeof activateHumanConfirmedLearningDecision;
|
|
103
|
+
confirmKnowledgeClaim?: typeof confirmRememberKnowledgeClaim;
|
|
104
|
+
proposalSummary?: typeof getWorkspaceKnowledgeChangeProposalSummary;
|
|
105
|
+
claimInitiatingHuman?: typeof getWorkspaceKnowledgeClaimInitiatingHuman;
|
|
106
|
+
instructionPolicyBaseline?: typeof getWorkspaceInstructionPolicyBaseline;
|
|
107
|
+
notifyActivation?: (input: {
|
|
108
|
+
db: Database;
|
|
109
|
+
receipt: GovernedLearningActivationReceipt;
|
|
110
|
+
sessionId: string;
|
|
111
|
+
attemptId: string;
|
|
112
|
+
}) => Promise<unknown>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Deterministic UUID-shaped id derived from the caller's remember operation id. */
|
|
116
|
+
export function derivedRememberOperationId(
|
|
117
|
+
operationId: string,
|
|
118
|
+
stage: "note" | "note-archive" | "promotion" | "evaluation" | "activation" | "rebaseline",
|
|
119
|
+
): string {
|
|
120
|
+
const bytes = Buffer.from(
|
|
121
|
+
createHash("sha256")
|
|
122
|
+
.update(`company-brain-remember:v1:${operationId}:${stage}`, "utf8")
|
|
123
|
+
.digest("hex")
|
|
124
|
+
.slice(0, 32),
|
|
125
|
+
"hex",
|
|
126
|
+
);
|
|
127
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x50;
|
|
128
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
129
|
+
const hex = bytes.toString("hex");
|
|
130
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function normalizedKeyFor(noteId: string): string {
|
|
134
|
+
return `remember-${noteId.replaceAll("-", "")}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function promotionRequest(
|
|
138
|
+
request: RememberRequest,
|
|
139
|
+
noteId: string,
|
|
140
|
+
instructionBaseline: {
|
|
141
|
+
expectedCurrentRevisionId: string | null;
|
|
142
|
+
expectedActivationVersion: number;
|
|
143
|
+
},
|
|
144
|
+
): CompanyBrainGovernedWriteRequest {
|
|
145
|
+
const common = {
|
|
146
|
+
operationId: derivedRememberOperationId(request.operationId, "promotion"),
|
|
147
|
+
noteId,
|
|
148
|
+
expectedNoteVersion: 1 as const,
|
|
149
|
+
// The user directed this write, so the claim carries full confidence.
|
|
150
|
+
confidenceBps: 10_000,
|
|
151
|
+
reason: request.reason,
|
|
152
|
+
};
|
|
153
|
+
switch (request.lane) {
|
|
154
|
+
case "preference":
|
|
155
|
+
return {
|
|
156
|
+
...common,
|
|
157
|
+
kind: "promote_task_note_preference",
|
|
158
|
+
entityType: "ways-of-working",
|
|
159
|
+
normalizedKey: normalizedKeyFor(noteId),
|
|
160
|
+
displayName: request.title,
|
|
161
|
+
predicateKey: "remember.preference",
|
|
162
|
+
stableKey: request.stableKey,
|
|
163
|
+
title: request.title,
|
|
164
|
+
description: request.description,
|
|
165
|
+
precedenceRank: 0,
|
|
166
|
+
conflictStrategy: "override",
|
|
167
|
+
conflictsWith: [],
|
|
168
|
+
expiresAt: null,
|
|
169
|
+
};
|
|
170
|
+
case "instruction_policy":
|
|
171
|
+
return {
|
|
172
|
+
...common,
|
|
173
|
+
kind: "promote_task_note_instruction_policy",
|
|
174
|
+
entityType: "ways-of-working",
|
|
175
|
+
normalizedKey: normalizedKeyFor(noteId),
|
|
176
|
+
displayName: "User-directed rule",
|
|
177
|
+
predicateKey: "remember.instruction",
|
|
178
|
+
target: request.target,
|
|
179
|
+
expectedCurrentRevisionId: instructionBaseline.expectedCurrentRevisionId,
|
|
180
|
+
expectedActivationVersion: instructionBaseline.expectedActivationVersion,
|
|
181
|
+
};
|
|
182
|
+
case "knowledge":
|
|
183
|
+
return {
|
|
184
|
+
...common,
|
|
185
|
+
kind: "promote_task_note_knowledge",
|
|
186
|
+
entityType: "user-directed",
|
|
187
|
+
normalizedKey: normalizedKeyFor(noteId),
|
|
188
|
+
displayName: request.subject,
|
|
189
|
+
predicateKey: "remember.fact",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Bound for the card title; `HumanInputQuestion.label` accepts 128 characters. */
|
|
195
|
+
const REMEMBER_LABEL_MAX_CHARS = 128;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* What saving this costs, shown on the confirmation card so a human can judge
|
|
199
|
+
* the length before agreeing. A mandatory rule is composed into the prompt of
|
|
200
|
+
* every session it applies to; a preference contributes only its short
|
|
201
|
+
* descriptor to that prompt and keeps its content behind on-demand retrieval;
|
|
202
|
+
* knowledge is retrieval evidence and never joins the always-composed prefix.
|
|
203
|
+
*
|
|
204
|
+
* `HumanInputForm` renders a single question's `label` as the card heading and
|
|
205
|
+
* its `prompt` as the sub-text, so this stays a title with a parenthetical
|
|
206
|
+
* rather than a sentence: the question the human answers has to keep reading as
|
|
207
|
+
* the heading of the card.
|
|
208
|
+
*
|
|
209
|
+
* This deliberately lives on `label` rather than `helpText`: migrations 0272 /
|
|
210
|
+
* 0274 / 0284 / 0293 / 0316 byte-verify the reconstructed `prompt`, `helpText`,
|
|
211
|
+
* and `options` against the exact Task-note text before authorizing an
|
|
212
|
+
* activation, and `label` is the one field of the canonical question those
|
|
213
|
+
* capabilities do not constrain.
|
|
214
|
+
*/
|
|
215
|
+
export function rememberConfirmationLabel(input: {
|
|
216
|
+
lane: RememberLane;
|
|
217
|
+
contentChars: number;
|
|
218
|
+
/** Role-scoped rules compose only for sessions bound to that role. */
|
|
219
|
+
policyScope?: "global" | "role" | undefined;
|
|
220
|
+
}): string {
|
|
221
|
+
const cost =
|
|
222
|
+
input.lane === "instruction_policy"
|
|
223
|
+
? input.policyScope === "role"
|
|
224
|
+
? "in every prompt for this role"
|
|
225
|
+
: "in every session prompt"
|
|
226
|
+
: input.lane === "preference"
|
|
227
|
+
? "summary in every prompt"
|
|
228
|
+
: "retrieved when relevant";
|
|
229
|
+
return `Remember (${input.contentChars} chars, ${cost})`.slice(0, REMEMBER_LABEL_MAX_CHARS);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Canonical confirmation question. Migration 0272 reconstructs exactly this
|
|
234
|
+
* prompt/help/options from the proposal and refuses any human-input row that
|
|
235
|
+
* differs, so a misleading agent-authored prompt can never authorize a save.
|
|
236
|
+
* `helpText` truncation is by code point to match PostgreSQL `left()`.
|
|
237
|
+
*/
|
|
238
|
+
function humanInputPrompt(request: RememberRequest, targetId: string) {
|
|
239
|
+
const prompt =
|
|
240
|
+
request.lane === "preference"
|
|
241
|
+
? "Save this as a workspace preference for everyone in this workspace?"
|
|
242
|
+
: request.lane === "instruction_policy"
|
|
243
|
+
? "Save this as a mandatory workspace rule for everyone in this workspace?"
|
|
244
|
+
: "Save this as workspace knowledge for everyone in this workspace?";
|
|
245
|
+
return {
|
|
246
|
+
questions: [
|
|
247
|
+
{
|
|
248
|
+
id: rememberHumanInputQuestionId(targetId),
|
|
249
|
+
kind: "single_select" as const,
|
|
250
|
+
prompt,
|
|
251
|
+
label: rememberConfirmationLabel({
|
|
252
|
+
lane: request.lane,
|
|
253
|
+
contentChars: Array.from(request.content).length,
|
|
254
|
+
policyScope: request.lane === "instruction_policy" ? request.target.scope : undefined,
|
|
255
|
+
}),
|
|
256
|
+
helpText: Array.from(request.content).slice(0, 2_000).join(""),
|
|
257
|
+
options: [
|
|
258
|
+
{ id: REMEMBER_HUMAN_INPUT_SAVE_OPTION, label: "Save" },
|
|
259
|
+
{ id: REMEMBER_HUMAN_INPUT_SKIP_OPTION, label: "Don't save" },
|
|
260
|
+
],
|
|
261
|
+
required: true,
|
|
262
|
+
allowOther: false,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
allowSkip: false as const,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function activationSummary(receipt: GovernedLearningActivationReceipt) {
|
|
270
|
+
return {
|
|
271
|
+
receiptId: receipt.id,
|
|
272
|
+
destination: receipt.destination,
|
|
273
|
+
destinationRevisionId: receipt.destinationRevisionId,
|
|
274
|
+
effectiveAt: receipt.effectiveAt,
|
|
275
|
+
authorityKind: receipt.authorityKind,
|
|
276
|
+
undo: "learning_history" as const,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Explicit user-directed remember. Content becomes an exact task note (the
|
|
282
|
+
* evidence), the note is promoted through the frozen learning policy router,
|
|
283
|
+
* and the result is either an immediate activation (automatic policy), a
|
|
284
|
+
* proposal for the human Knowledge review lifecycle, or a durable proposal that
|
|
285
|
+
* still needs one bound human confirmation through `request_human_input`.
|
|
286
|
+
*/
|
|
287
|
+
export function createRememberRouter(options: RememberRouterOptions): {
|
|
288
|
+
remember: (input: { attempt: unknown; request: unknown }) => Promise<RememberReceiptType>;
|
|
289
|
+
confirm: (input: { attempt: unknown; request: unknown }) => Promise<RememberConfirmReceiptType>;
|
|
290
|
+
} {
|
|
291
|
+
const learningRouter =
|
|
292
|
+
options.learningRouter ?? createCompanyBrainLearningPolicyRouter({ db: options.db });
|
|
293
|
+
const createNote = options.createNote ?? createTaskNote;
|
|
294
|
+
const archiveNote = options.archiveNote ?? archiveTaskNote;
|
|
295
|
+
const rebaselineProposal =
|
|
296
|
+
options.rebaselineProposal ?? rebaselineWorkspaceInstructionPolicyKnowledgeProposal;
|
|
297
|
+
const activateHumanConfirmed =
|
|
298
|
+
options.activateHumanConfirmed ?? activateHumanConfirmedLearningDecision;
|
|
299
|
+
const confirmKnowledgeClaim = options.confirmKnowledgeClaim ?? confirmRememberKnowledgeClaim;
|
|
300
|
+
const proposalSummary = options.proposalSummary ?? getWorkspaceKnowledgeChangeProposalSummary;
|
|
301
|
+
const claimInitiatingHuman =
|
|
302
|
+
options.claimInitiatingHuman ?? getWorkspaceKnowledgeClaimInitiatingHuman;
|
|
303
|
+
const instructionPolicyBaseline =
|
|
304
|
+
options.instructionPolicyBaseline ?? getWorkspaceInstructionPolicyBaseline;
|
|
305
|
+
const notifyActivation =
|
|
306
|
+
options.notifyActivation ??
|
|
307
|
+
(async ({ db, receipt, sessionId, attemptId }) =>
|
|
308
|
+
publishGovernedLearningEventToSlack(db, {
|
|
309
|
+
kind: "activated",
|
|
310
|
+
receipt,
|
|
311
|
+
sessionId,
|
|
312
|
+
attemptId,
|
|
313
|
+
}));
|
|
314
|
+
|
|
315
|
+
async function attemptOf(input: unknown): Promise<Attempt> {
|
|
316
|
+
return CompanyBrainGovernedWriteAttempt.parse(input);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
async remember(input) {
|
|
321
|
+
const attempt = await attemptOf(input.attempt);
|
|
322
|
+
const request = RememberRequest.parse(input.request);
|
|
323
|
+
const note = await createNote(options.db, {
|
|
324
|
+
...attempt,
|
|
325
|
+
operationId: derivedRememberOperationId(request.operationId, "note"),
|
|
326
|
+
kind: "decision",
|
|
327
|
+
text: request.content,
|
|
328
|
+
expiresInDays: TASK_NOTE_MAX_LIFETIME_DAYS,
|
|
329
|
+
});
|
|
330
|
+
// A user-directed rule must bind to the exact current activation
|
|
331
|
+
// baseline of its target; a workspace with an active head is the norm.
|
|
332
|
+
const instructionBaseline =
|
|
333
|
+
request.lane === "instruction_policy"
|
|
334
|
+
? await instructionPolicyBaseline(options.db, {
|
|
335
|
+
workspaceId: attempt.workspaceId,
|
|
336
|
+
target: request.target,
|
|
337
|
+
})
|
|
338
|
+
: { expectedCurrentRevisionId: null, expectedActivationVersion: 0 };
|
|
339
|
+
// The evidence note has to exist before the governed write (the write
|
|
340
|
+
// promotes it), so a failed write would otherwise strand a live note for
|
|
341
|
+
// its full lifetime with nothing pointing at it. Archive it on the way
|
|
342
|
+
// out, then surface the original failure.
|
|
343
|
+
let route: CompanyBrainLearningPolicyRouteReceipt;
|
|
344
|
+
try {
|
|
345
|
+
route = await learningRouter.write({
|
|
346
|
+
attempt,
|
|
347
|
+
request: promotionRequest(request, note.note.id, instructionBaseline),
|
|
348
|
+
});
|
|
349
|
+
} catch (error) {
|
|
350
|
+
await archiveNote(options.db, {
|
|
351
|
+
...attempt,
|
|
352
|
+
operationId: derivedRememberOperationId(request.operationId, "note-archive"),
|
|
353
|
+
noteId: note.note.id,
|
|
354
|
+
expectedVersion: note.note.version,
|
|
355
|
+
reason: "The remember write this note was created for did not complete.",
|
|
356
|
+
}).catch(() => undefined);
|
|
357
|
+
throw asRememberFailure(error);
|
|
358
|
+
}
|
|
359
|
+
const base = {
|
|
360
|
+
operationId: request.operationId,
|
|
361
|
+
lane: request.lane,
|
|
362
|
+
scope: request.scope,
|
|
363
|
+
};
|
|
364
|
+
if (route.decision === "blocked" || route.write === null) {
|
|
365
|
+
return RememberReceipt.parse({ ...base, status: "blocked", reason: "learning_policy_off" });
|
|
366
|
+
}
|
|
367
|
+
if (route.write.knowledgeChangeProposalId === null) {
|
|
368
|
+
// Knowledge is owned by the human review lifecycle: the same one-click
|
|
369
|
+
// confirmation approves the claim, bound to the claim id.
|
|
370
|
+
return RememberReceipt.parse({
|
|
371
|
+
...base,
|
|
372
|
+
status: "confirmation_required",
|
|
373
|
+
noteId: note.note.id,
|
|
374
|
+
proposalId: null,
|
|
375
|
+
claimId: route.write.claimId,
|
|
376
|
+
learning: null,
|
|
377
|
+
learningFailure: null,
|
|
378
|
+
humanInput: humanInputPrompt(request, route.write.claimId),
|
|
379
|
+
confirmWith: "remember_confirm",
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
const proposalId = route.write.knowledgeChangeProposalId;
|
|
383
|
+
if (route.activation.activated && route.activation.receiptId) {
|
|
384
|
+
return RememberReceipt.parse({
|
|
385
|
+
...base,
|
|
386
|
+
status: "activated",
|
|
387
|
+
noteId: note.note.id,
|
|
388
|
+
proposalId,
|
|
389
|
+
learning: route.learning,
|
|
390
|
+
activation: {
|
|
391
|
+
receiptId: route.activation.receiptId,
|
|
392
|
+
destination: route.activation.destination,
|
|
393
|
+
destinationRevisionId: route.activation.destinationRevisionId,
|
|
394
|
+
effectiveAt: route.activation.effectiveAt,
|
|
395
|
+
authorityKind: "automatic",
|
|
396
|
+
undo: "learning_history",
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return RememberReceipt.parse({
|
|
401
|
+
...base,
|
|
402
|
+
status: "confirmation_required",
|
|
403
|
+
noteId: note.note.id,
|
|
404
|
+
proposalId,
|
|
405
|
+
claimId: route.write.claimId,
|
|
406
|
+
learning: route.learning,
|
|
407
|
+
learningFailure: route.learningFailure,
|
|
408
|
+
humanInput: humanInputPrompt(request, proposalId),
|
|
409
|
+
confirmWith: "remember_confirm",
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
async confirm(input) {
|
|
414
|
+
const attempt = await attemptOf(input.attempt);
|
|
415
|
+
const request = RememberConfirmRequest.parse(input.request);
|
|
416
|
+
if (request.target === "knowledge_claim") {
|
|
417
|
+
// The database capability proves the exact human answered the bound
|
|
418
|
+
// question with `save` on this live turn and that the claim is still
|
|
419
|
+
// awaiting review before writing the approval.
|
|
420
|
+
const initiatingHuman = await claimInitiatingHuman(options.db, {
|
|
421
|
+
workspaceId: attempt.workspaceId,
|
|
422
|
+
claimId: request.claimId,
|
|
423
|
+
});
|
|
424
|
+
if (!initiatingHuman) {
|
|
425
|
+
throw new RememberError("proposal_unavailable", "The remember claim is not available");
|
|
426
|
+
}
|
|
427
|
+
const receipt = await confirmKnowledgeClaim(options.db, {
|
|
428
|
+
caller: {
|
|
429
|
+
workspaceId: attempt.workspaceId,
|
|
430
|
+
subjectId: initiatingHuman,
|
|
431
|
+
sessionId: attempt.sessionId,
|
|
432
|
+
turnId: attempt.turnId,
|
|
433
|
+
executionGeneration: attempt.executionGeneration,
|
|
434
|
+
},
|
|
435
|
+
request: {
|
|
436
|
+
operationId: request.operationId,
|
|
437
|
+
claimId: request.claimId,
|
|
438
|
+
humanInputRequestId: request.humanInputRequestId,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
return RememberConfirmReceipt.parse({
|
|
442
|
+
status: "activated",
|
|
443
|
+
operationId: request.operationId,
|
|
444
|
+
proposalId: null,
|
|
445
|
+
claimId: receipt.claimId,
|
|
446
|
+
decisionReceiptId: null,
|
|
447
|
+
activation: {
|
|
448
|
+
destination: "knowledge",
|
|
449
|
+
receiptId: receipt.id,
|
|
450
|
+
claimId: receipt.claimId,
|
|
451
|
+
approvalReviewId: receipt.approvalReviewId,
|
|
452
|
+
effectiveAt: receipt.createdAt,
|
|
453
|
+
authorityKind: "human_confirmed",
|
|
454
|
+
undo: "knowledge_review",
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
const proposal = await proposalSummary(options.db, {
|
|
459
|
+
workspaceId: attempt.workspaceId,
|
|
460
|
+
proposalId: request.proposalId,
|
|
461
|
+
});
|
|
462
|
+
if (!proposal || !proposal.initiatingHumanSubjectId) {
|
|
463
|
+
throw new RememberError("proposal_unavailable", "The remember proposal is not available");
|
|
464
|
+
}
|
|
465
|
+
if (proposal.status !== "proposed") {
|
|
466
|
+
throw new RememberError(
|
|
467
|
+
"proposal_not_confirmable",
|
|
468
|
+
"The remember proposal is no longer awaiting confirmation",
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
// The database capability proves the exact human answered the bound
|
|
472
|
+
// question with `save` on this session/turn generation before writing.
|
|
473
|
+
// The human has already answered "save". If the head moved since this
|
|
474
|
+
// rule was proposed, re-asking them would be the alternative; instead
|
|
475
|
+
// rebaseline onto the current head and retry. The successor reuses this
|
|
476
|
+
// same knowledge proposal, so the confirmation stays bound to exactly the
|
|
477
|
+
// content the human approved. Bounded, because each retry can lose
|
|
478
|
+
// another race with a concurrent activation.
|
|
479
|
+
const activateOnce = async (): Promise<GovernedLearningActivationReceipt> =>
|
|
480
|
+
await activateHumanConfirmed(options.db, {
|
|
481
|
+
caller: {
|
|
482
|
+
workspaceId: attempt.workspaceId,
|
|
483
|
+
subjectId: proposal.initiatingHumanSubjectId!,
|
|
484
|
+
},
|
|
485
|
+
request: {
|
|
486
|
+
operationId: derivedGovernedLearningOperationId(request.operationId, "activation"),
|
|
487
|
+
decisionReceiptId: request.decisionReceiptId,
|
|
488
|
+
humanInputRequestId: request.humanInputRequestId,
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
let activation: GovernedLearningActivationReceipt | null = null;
|
|
492
|
+
let lastFailure: unknown;
|
|
493
|
+
for (
|
|
494
|
+
let attemptIndex = 0;
|
|
495
|
+
attemptIndex < REBASELINE_ATTEMPTS && !activation;
|
|
496
|
+
attemptIndex++
|
|
497
|
+
) {
|
|
498
|
+
try {
|
|
499
|
+
activation = await activateOnce();
|
|
500
|
+
} catch (error) {
|
|
501
|
+
lastFailure = error;
|
|
502
|
+
if (
|
|
503
|
+
proposal.targetKind !== "instruction_policy" ||
|
|
504
|
+
!(asRememberFailure(error) instanceof RememberError) ||
|
|
505
|
+
attemptIndex === REBASELINE_ATTEMPTS - 1
|
|
506
|
+
) {
|
|
507
|
+
throw asRememberFailure(error);
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
await rebaselineProposal(options.db, {
|
|
511
|
+
accountId: attempt.accountId,
|
|
512
|
+
workspaceId: attempt.workspaceId,
|
|
513
|
+
knowledgeProposalId: proposal.id,
|
|
514
|
+
initiatingHumanSubjectId: proposal.initiatingHumanSubjectId!,
|
|
515
|
+
operationId: derivedRememberOperationId(
|
|
516
|
+
`${request.operationId}:${attemptIndex}`,
|
|
517
|
+
"rebaseline",
|
|
518
|
+
),
|
|
519
|
+
});
|
|
520
|
+
} catch {
|
|
521
|
+
// The rebaseline is the recovery, not the outcome the caller asked
|
|
522
|
+
// for. If it cannot land, the honest answer is still the actionable
|
|
523
|
+
// stale-baseline failure rather than an internal recovery error.
|
|
524
|
+
throw asRememberFailure(error);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (!activation) throw asRememberFailure(lastFailure);
|
|
529
|
+
try {
|
|
530
|
+
await notifyActivation({
|
|
531
|
+
db: options.db,
|
|
532
|
+
receipt: activation,
|
|
533
|
+
sessionId: attempt.sessionId,
|
|
534
|
+
attemptId: attempt.attemptId,
|
|
535
|
+
});
|
|
536
|
+
} catch {
|
|
537
|
+
// Notification is best-effort; the durable receipts already exist.
|
|
538
|
+
}
|
|
539
|
+
return RememberConfirmReceipt.parse({
|
|
540
|
+
status: "activated",
|
|
541
|
+
operationId: request.operationId,
|
|
542
|
+
proposalId: proposal.id,
|
|
543
|
+
claimId: proposal.claimId,
|
|
544
|
+
decisionReceiptId: request.decisionReceiptId,
|
|
545
|
+
activation: activationSummary(activation),
|
|
546
|
+
});
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
}
|