@opengeni/core 1.0.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.
Files changed (99) hide show
  1. package/dist/access/index.d.ts +37 -0
  2. package/dist/application/new-session-drafts.d.ts +8 -1
  3. package/dist/application/session-commands.d.ts +18 -11
  4. package/dist/application/session-tenancy.d.ts +18 -0
  5. package/dist/application/user-resource-grants.d.ts +20 -0
  6. package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
  7. package/dist/chunk-H7X52RI2.js.map +1 -0
  8. package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
  9. package/dist/chunk-VCZFFEPB.js.map +1 -0
  10. package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
  11. package/dist/chunk-ZYCNLYGH.js.map +1 -0
  12. package/dist/dependencies.d.ts +12 -1
  13. package/dist/domain/automations.d.ts +51 -0
  14. package/dist/domain/company-brain-governed-writes.d.ts +70 -0
  15. package/dist/domain/company-profile-agent-admin.d.ts +24 -0
  16. package/dist/domain/connection-authority.d.ts +97 -0
  17. package/dist/domain/connection-authority.js +169 -0
  18. package/dist/domain/connection-authority.js.map +1 -0
  19. package/dist/domain/editable-artifacts/service.d.ts +1 -2
  20. package/dist/domain/environments.d.ts +1 -1
  21. package/dist/domain/github-repository-bindings.d.ts +117 -0
  22. package/dist/domain/governed-learning-activation.d.ts +16 -0
  23. package/dist/domain/governed-learning-evaluator.d.ts +6 -0
  24. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  25. package/dist/domain/memory-slack-delivery.d.ts +1 -4
  26. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  27. package/dist/domain/packs.d.ts +5 -1
  28. package/dist/domain/personal-connection-delegations.d.ts +34 -2
  29. package/dist/domain/pr-review.d.ts +149 -0
  30. package/dist/domain/remember.d.ts +69 -0
  31. package/dist/domain/resources.d.ts +22 -1
  32. package/dist/domain/scheduled-tasks.d.ts +43 -2
  33. package/dist/domain/session-tool-policy.d.ts +1 -1
  34. package/dist/domain/sessions.d.ts +40 -6
  35. package/dist/editable-artifact-live/application.d.ts +10 -2
  36. package/dist/editable-artifact-live/ports.d.ts +0 -1
  37. package/dist/editable-artifact-live/server.d.ts +0 -1
  38. package/dist/editable-artifact-live/ticket.d.ts +0 -1
  39. package/dist/editable-artifact-live/types.d.ts +5 -2
  40. package/dist/editable-artifact-live.js +2 -2
  41. package/dist/editable-artifacts.js +2 -4
  42. package/dist/index.d.ts +13 -1
  43. package/dist/index.js +4827 -1164
  44. package/dist/index.js.map +1 -1
  45. package/dist/rigs/index.d.ts +22 -5
  46. package/dist/sandbox/fleet.d.ts +34 -6
  47. package/dist/sandbox/routing.d.ts +15 -1
  48. package/dist/sandbox/runtime-settings.d.ts +39 -0
  49. package/package.json +16 -11
  50. package/src/access/index.ts +151 -31
  51. package/src/application/new-session-drafts.ts +24 -2
  52. package/src/application/session-commands.ts +305 -71
  53. package/src/application/session-tenancy.ts +321 -0
  54. package/src/application/user-resource-grants.ts +140 -0
  55. package/src/billing/limits.ts +25 -15
  56. package/src/dependencies.ts +12 -1
  57. package/src/domain/automations.ts +196 -0
  58. package/src/domain/capabilities.ts +7 -1
  59. package/src/domain/company-brain-governed-writes.ts +330 -0
  60. package/src/domain/company-profile-agent-admin.ts +55 -0
  61. package/src/domain/connection-authority.ts +329 -0
  62. package/src/domain/editable-artifacts/agent-application.ts +17 -9
  63. package/src/domain/editable-artifacts/service.ts +12 -1
  64. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  65. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  66. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  67. package/src/domain/environments.ts +16 -3
  68. package/src/domain/github-repository-bindings.ts +295 -0
  69. package/src/domain/governed-learning-activation.ts +44 -0
  70. package/src/domain/governed-learning-evaluator.ts +49 -0
  71. package/src/domain/governed-learning-slack-publication.ts +157 -0
  72. package/src/domain/insights.ts +9 -0
  73. package/src/domain/memory-slack-delivery.ts +0 -30
  74. package/src/domain/organization-membership-lifecycle.ts +9 -0
  75. package/src/domain/packs.ts +106 -3
  76. package/src/domain/personal-connection-delegations.ts +532 -51
  77. package/src/domain/pr-review.ts +671 -0
  78. package/src/domain/remember.ts +549 -0
  79. package/src/domain/resources.ts +131 -4
  80. package/src/domain/scheduled-tasks.ts +497 -76
  81. package/src/domain/session-tool-policy.ts +12 -9
  82. package/src/domain/sessions.ts +613 -166
  83. package/src/editable-artifact-live/application.ts +21 -3
  84. package/src/editable-artifact-live/ports.ts +0 -1
  85. package/src/editable-artifact-live/server.ts +13 -7
  86. package/src/editable-artifact-live/ticket.ts +1 -5
  87. package/src/editable-artifact-live/types.ts +6 -2
  88. package/src/editable-artifact-live/wire.ts +0 -10
  89. package/src/index.ts +13 -1
  90. package/src/rigs/index.ts +165 -48
  91. package/src/sandbox/fleet.ts +376 -30
  92. package/src/sandbox/routing.ts +91 -29
  93. package/src/sandbox/runtime-settings.ts +274 -0
  94. package/src/session-authorization.ts +17 -49
  95. package/dist/chunk-6WKPWE5S.js.map +0 -1
  96. package/dist/chunk-JJU6I5XD.js.map +0 -1
  97. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  98. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  99. 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
+ }