@dsh-enhanced/assistant-memory-learning 0.0.0-stage → 0.1.53

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.
@@ -0,0 +1,116 @@
1
+ import type { AssistantAutomationsService } from '@dsh-enhanced/assistant-automations';
2
+ import type { AssistantDeliveryService, OwnerForegroundTaskSource, OwnerForegroundTaskSourceScope } from '@dsh-enhanced/assistant-delivery';
3
+ import type { AssistantEvaluationService } from '@dsh-enhanced/assistant-evaluation';
4
+ import type { AssistantPolicyService } from '@dsh-enhanced/assistant-policy';
5
+ import type { MemoryReviewReceipt } from '@dsh-enhanced/assistant-verifier';
6
+ import { type MemoryLearningProposal, type MemoryLearningReviewRequest } from '@dsh-enhanced/assistant-growth-contract';
7
+ import type { LearningConfig, LearningJob, LearningSnapshot } from './types.js';
8
+ type Delivery = Pick<AssistantDeliveryService, 'validateOwnerRoute' | 'listOwnerForegroundTaskSources' | 'withOwnerForegroundTaskSourcesFence' | 'inspectOwnerForegroundLearningTask'> & {
9
+ inspectOwnerForegroundTaskSource(input: OwnerForegroundTaskSourceScope & {
10
+ inboxId: string;
11
+ }): Readonly<OwnerForegroundTaskSource> | undefined;
12
+ };
13
+ type Evaluation = Pick<AssistantEvaluationService, 'canonicalHostScope' | 'listTrustedTaskLearningProjections' | 'withTrustedCanonicalScopeWriterFence' | 'getTrustedForegroundLearningProjection' | 'inspectTrustedTaskOwnerRevision'>;
14
+ type Automations = Pick<AssistantAutomationsService, 'registerHostExecutor' | 'reconcileSystem' | 'inspectSystemOwnedActivation' | 'inspectSystemOwned'>;
15
+ type Target = LearningSnapshot['targets'][number];
16
+ interface Memory {
17
+ listLearningTargets(input: {
18
+ owner: LearningConfig['owner'];
19
+ limit?: number;
20
+ }): readonly Target[];
21
+ inspectLearningAdoptionAvailability(input: {
22
+ owner: LearningConfig['owner'];
23
+ }): Readonly<{
24
+ authorityId: string;
25
+ grantDigest: string;
26
+ expiresAt: number;
27
+ remainingMutations: number;
28
+ remainingContentBytes: number;
29
+ available: boolean;
30
+ }>;
31
+ lookupLearningAdoption(input: {
32
+ request: MemoryLearningReviewRequest;
33
+ }): Readonly<{
34
+ receiptDigest: string;
35
+ }> | undefined;
36
+ adoptReviewedLearning(input: {
37
+ request: MemoryLearningReviewRequest;
38
+ }): Readonly<{
39
+ receiptDigest: string;
40
+ }>;
41
+ }
42
+ interface Verifier {
43
+ inspectMemoryLearningReviewAvailability(input: {
44
+ owner: LearningConfig['owner'];
45
+ }): Readonly<{
46
+ authorityId: string;
47
+ authorityDigest: string;
48
+ expiresAt: number;
49
+ remainingReviews: number;
50
+ available: boolean;
51
+ }> | undefined;
52
+ reviewMemoryLearning(request: MemoryLearningReviewRequest, signal?: AbortSignal): Promise<MemoryReviewReceipt>;
53
+ lookupMemoryLearningReview(request: MemoryLearningReviewRequest): Readonly<MemoryReviewReceipt> | undefined;
54
+ }
55
+ export interface LearningPorts {
56
+ delivery: Delivery;
57
+ evaluation: Evaluation;
58
+ automations: Automations;
59
+ memory: Memory;
60
+ verifier: Verifier;
61
+ policy: Pick<AssistantPolicyService, 'evaluate' | 'authorize'>;
62
+ extract(input: {
63
+ config: LearningConfig;
64
+ job: LearningJob;
65
+ signal: AbortSignal;
66
+ assertCurrent(): void;
67
+ }): Promise<{
68
+ proposal: MemoryLearningProposal | null;
69
+ reason: string;
70
+ sessionId: string;
71
+ outputDigest: string;
72
+ }>;
73
+ }
74
+ /** Two durable source cursors. Native Automations owns every timer and dispatch. */
75
+ export declare class MemoryLearningRuntime {
76
+ private readonly ports;
77
+ private readonly config;
78
+ private readonly store;
79
+ private readonly configDigest;
80
+ private readonly lane;
81
+ private readonly scanId;
82
+ private readonly abort;
83
+ private readonly flights;
84
+ private unregister;
85
+ private active;
86
+ private scanning;
87
+ private lastError;
88
+ private lastScanAt;
89
+ private closing;
90
+ constructor(config: LearningConfig, ports: LearningPorts);
91
+ health: () => {
92
+ remainingExtractions: number;
93
+ available: boolean;
94
+ enabled: boolean;
95
+ connected: boolean;
96
+ lastScanAt: number | null;
97
+ lastError: string | null;
98
+ counts: Record<import("./types.js").LearningState, number>;
99
+ };
100
+ start(): void;
101
+ close(): Promise<void>;
102
+ private scope;
103
+ private owner;
104
+ private policy;
105
+ private available;
106
+ private evaluationFence;
107
+ private withSource;
108
+ scan: () => void;
109
+ private definition;
110
+ private schedule;
111
+ private reviewMatches;
112
+ private reconcile;
113
+ private execute;
114
+ }
115
+ export {};
116
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
package/lib/runtime.js ADDED
@@ -0,0 +1,474 @@
1
+ import { growthObjectDigest, memoryLearningRequestDigest, validateMemoryLearningReviewRequest } from '@dsh-enhanced/assistant-growth-contract';
2
+ import { validateLearningConfig, validateLearningModel } from './config.js';
3
+ import { LearningStore } from './store.js';
4
+ const OWNER = 'assistant-memory-learning';
5
+ const EXECUTOR = 'assistant-memory-learning-v1';
6
+ const CATALOG = growthObjectDigest({ executor: EXECUTOR, contract: 1 });
7
+ class SourcePending extends Error {
8
+ }
9
+ class SourceObsolete extends Error {
10
+ }
11
+ const same = (a, b) => growthObjectDigest(a) === growthObjectDigest(b);
12
+ const reasonText = (value) => Array.from(String(value).normalize('NFC').replaceAll('\0', '').trim())
13
+ .slice(0, 120).join('') || 'learning-incomplete';
14
+ /** Two durable source cursors. Native Automations owns every timer and dispatch. */
15
+ export class MemoryLearningRuntime {
16
+ ports;
17
+ config;
18
+ store;
19
+ configDigest;
20
+ lane;
21
+ scanId;
22
+ abort = new AbortController();
23
+ flights = new Set();
24
+ unregister;
25
+ active = false;
26
+ scanning = false;
27
+ lastError = null;
28
+ lastScanAt = null;
29
+ closing;
30
+ constructor(config, ports) {
31
+ this.ports = ports;
32
+ this.config = validateLearningConfig(config);
33
+ this.configDigest = growthObjectDigest(this.config);
34
+ this.lane = growthObjectDigest([this.config.authorityId, this.config.owner]);
35
+ this.scanId = `memory-scan-${this.lane}`;
36
+ for (const [port, methods] of [
37
+ [ports.delivery, ['validateOwnerRoute', 'listOwnerForegroundTaskSources', 'inspectOwnerForegroundTaskSource', 'withOwnerForegroundTaskSourcesFence', 'inspectOwnerForegroundLearningTask']],
38
+ [ports.evaluation, ['canonicalHostScope', 'listTrustedTaskLearningProjections', 'withTrustedCanonicalScopeWriterFence', 'getTrustedForegroundLearningProjection', 'inspectTrustedTaskOwnerRevision']],
39
+ [ports.automations, ['registerHostExecutor', 'reconcileSystem', 'inspectSystemOwnedActivation', 'inspectSystemOwned']],
40
+ [ports.memory, ['listLearningTargets', 'inspectLearningAdoptionAvailability', 'lookupLearningAdoption', 'adoptReviewedLearning']],
41
+ [ports.verifier, ['inspectMemoryLearningReviewAvailability', 'reviewMemoryLearning', 'lookupMemoryLearningReview']],
42
+ [ports.policy, ['evaluate', 'authorize']],
43
+ ])
44
+ for (const method of methods)
45
+ if (typeof port[method] !== 'function')
46
+ throw new Error(`memory learning requires compatible Host API: ${method}`);
47
+ this.store = new LearningStore(this.config.databasePath, { authorityId: this.config.authorityId,
48
+ configDigest: this.configDigest, maxExtractions: this.config.maxExtractions });
49
+ }
50
+ health = () => ({ enabled: true, connected: this.active, lastScanAt: this.lastScanAt,
51
+ lastError: this.lastError, counts: this.store.counts(this.lane), ...this.store.availability() });
52
+ start() {
53
+ if (this.active || this.closing)
54
+ throw new Error('memory learning runtime cannot be started twice');
55
+ try {
56
+ this.store.interrupt(this.lane);
57
+ this.unregister = this.ports.automations.registerHostExecutor({
58
+ descriptor: { executorId: EXECUTOR, contractVersion: 1, catalogDigest: CATALOG },
59
+ accepts: spec => spec.executorId === EXECUTOR && spec.executorContractVersion === 1 && spec.catalogDigest === CATALOG
60
+ && spec.runbookVersion === 1 && ['scan', 'learn'].includes(spec.runbookId),
61
+ execute: input => {
62
+ const flight = this.execute(input);
63
+ this.flights.add(flight);
64
+ void flight.finally(() => this.flights.delete(flight)).catch(() => { });
65
+ return flight;
66
+ },
67
+ });
68
+ this.active = true;
69
+ this.owner();
70
+ this.ports.automations.reconcileSystem({ owner: OWNER, automationId: this.scanId,
71
+ idempotencyKey: `${this.scanId}:${this.configDigest}`, desiredStatus: 'active', definition: this.definition() });
72
+ // One bounded activation reconciliation; subsequent scans are native jobs.
73
+ this.scan();
74
+ }
75
+ catch (error) {
76
+ this.active = false;
77
+ this.unregister?.();
78
+ this.abort.abort(error);
79
+ this.store.close();
80
+ throw error;
81
+ }
82
+ }
83
+ close() {
84
+ return this.closing ??= (async () => {
85
+ this.active = false;
86
+ this.unregister?.();
87
+ this.abort.abort(new Error('memory learning provider disposed'));
88
+ this.store.interrupt(this.lane);
89
+ const deadline = AbortSignal.timeout(5000);
90
+ let release;
91
+ const expired = new Promise(resolve => {
92
+ const timeout = () => resolve(false);
93
+ deadline.addEventListener('abort', timeout, { once: true });
94
+ release = () => deadline.removeEventListener('abort', timeout);
95
+ });
96
+ try {
97
+ const drained = await Promise.race([Promise.allSettled(this.flights).then(() => true), expired]);
98
+ if (!drained)
99
+ throw new Error('memory learning shutdown timed out; pending dispatches remain unknown');
100
+ }
101
+ finally {
102
+ release();
103
+ this.store.close();
104
+ }
105
+ })();
106
+ }
107
+ scope() {
108
+ const owner = this.config.owner;
109
+ return { authorityId: owner.authorityId, principalId: owner.principalId, workspace: owner.workspace,
110
+ agentPreset: owner.agentPreset, expectedOwner: { authorityHash: owner.authorityHash,
111
+ principalRecordId: owner.principalRecordId, principalVersion: owner.principalVersion } };
112
+ }
113
+ owner() {
114
+ if (!this.active)
115
+ throw new Error('memory learning unavailable');
116
+ const current = this.ports.delivery.validateOwnerRoute(this.scope());
117
+ for (const key of Object.keys(this.config.owner)) {
118
+ if (current[key] !== this.config.owner[key])
119
+ throw new Error('memory learning owner authority changed');
120
+ }
121
+ }
122
+ policy(consume = false, idempotencyKey) {
123
+ const owner = this.config.owner;
124
+ const input = { subject: { kind: 'background', id: OWNER, workspace: owner.workspace, principal: owner.principalId },
125
+ action: 'extract', resource: { kind: 'memory', id: `learning:${this.config.authorityId}` }, context: { initiator: 'background' } };
126
+ const decision = consume ? this.ports.policy.authorize(input, idempotencyKey === undefined ? {} : { idempotencyKey }) : this.ports.policy.evaluate(input);
127
+ if (decision.effect !== 'allow')
128
+ throw new Error('memory learning extraction policy denied');
129
+ }
130
+ available() {
131
+ this.owner();
132
+ this.policy();
133
+ const review = this.ports.verifier.inspectMemoryLearningReviewAvailability({ owner: this.config.owner });
134
+ const memory = this.ports.memory.inspectLearningAdoptionAvailability({ owner: this.config.owner });
135
+ return Date.now() < this.config.expiresAt && this.store.availability().available && review?.available === true
136
+ && review.authorityId === this.config.reviewAuthorityId && review.authorityDigest === this.config.reviewAuthorityDigest
137
+ && memory.available && memory.authorityId === this.config.adoptionAuthorityId && memory.grantDigest === this.config.adoptionGrantDigest;
138
+ }
139
+ evaluationFence(callback) {
140
+ const owner = this.config.owner;
141
+ const scope = this.ports.evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
142
+ const { scopeWatermark } = this.ports.evaluation.listTrustedTaskLearningProjections({ scope, limit: 1 });
143
+ const result = this.ports.evaluation.withTrustedCanonicalScopeWriterFence({ scope, scopeWatermark }, callback);
144
+ if (!result.matched)
145
+ throw new SourcePending('canonical scope changed during read');
146
+ return result.value;
147
+ }
148
+ withSource(job, callback) {
149
+ this.abort.signal.throwIfAborted();
150
+ this.owner();
151
+ this.policy();
152
+ if (job.intent.configDigest !== this.configDigest || !same(job.intent.owner, this.config.owner)
153
+ || Date.now() >= Math.min(job.intent.expiresAt, this.config.expiresAt))
154
+ throw new SourceObsolete('learning authority or source window expired');
155
+ const metadata = this.ports.delivery.inspectOwnerForegroundTaskSource({ ...this.scope(), inboxId: job.intent.inboxId });
156
+ if (!metadata)
157
+ throw new SourcePending('completed source unavailable');
158
+ if (job.intent.expectedSourceDigest && metadata.sourceDigest !== job.intent.expectedSourceDigest
159
+ || job.snapshot && metadata.sourceDigest !== job.snapshot.source.sourceDigest
160
+ || metadata.execution.status !== 'succeeded' || !metadata.execution.quiescent
161
+ || metadata.execution.modelSelectionState !== 'frozen' || !metadata.execution.modelSelection)
162
+ throw new SourceObsolete('source completion or model changed');
163
+ return this.ports.delivery.withOwnerForegroundTaskSourcesFence({ ...this.scope(),
164
+ sources: [{ inboxId: metadata.inboxId, expectedSourceDigest: metadata.sourceDigest }] }, contents => this.evaluationFence(() => {
165
+ const owner = this.config.owner;
166
+ const scope = this.ports.evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
167
+ const canonical = this.ports.evaluation.getTrustedForegroundLearningProjection({ scope, inboxId: job.intent.inboxId });
168
+ const revision = canonical && this.ports.evaluation.inspectTrustedTaskOwnerRevision({ scope, outcomeId: canonical.triggerOutcomeId,
169
+ principalRecordId: owner.principalRecordId, principalVersion: owner.principalVersion });
170
+ if (revision?.action === 'withdraw')
171
+ throw new SourceObsolete('owner withdrew learning source');
172
+ const content = contents[0];
173
+ if (!content)
174
+ throw new SourcePending('original reply not yet readable');
175
+ if (content.input.truncated || content.reply.truncated)
176
+ throw new SourceObsolete('learning source exceeds text boundary');
177
+ let feedback;
178
+ if (job.intent.canonical) {
179
+ const expected = job.intent.canonical;
180
+ if (!canonical || canonical.projection.disposition !== 'upsert' || canonical.projection.version !== expected.version
181
+ || canonical.projection.digest !== expected.digest || canonical.objective?.status !== expected.objectiveStatus)
182
+ throw new SourceObsolete('canonical result changed');
183
+ const task = this.ports.delivery.inspectOwnerForegroundLearningTask({ authorityId: owner.authorityId, principalId: owner.principalId,
184
+ workspace: owner.workspace, agentPreset: owner.agentPreset, outcomeId: expected.outcomeId });
185
+ if (!task || task.judgement === 'unresolved' || !task.source.quiescent || task.source.truncated || task.feedback?.truncated)
186
+ throw new SourcePending('canonical owner source not yet readable');
187
+ if (task.source.inboxId !== job.intent.inboxId || task.ownerRevision?.action === 'withdraw'
188
+ || task.canonical.projection.version !== expected.version || task.canonical.projection.digest !== expected.digest
189
+ || task.canonical.objective?.status !== expected.objectiveStatus
190
+ || task.owner.authorityHash !== owner.authorityHash || task.owner.principalRecordId !== owner.principalRecordId
191
+ || task.owner.principalVersion !== owner.principalVersion)
192
+ throw new SourceObsolete('canonical source owner changed');
193
+ feedback = task.feedback?.text;
194
+ }
195
+ if (job.snapshot && (content.contentDigest !== job.snapshot.source.contentDigest
196
+ || content.input.text !== job.snapshot.ownerStatement || content.reply.text !== job.snapshot.assistantReply
197
+ || feedback !== job.snapshot.ownerFeedback
198
+ || !same(this.config.model ?? content.source.execution.modelSelection, job.snapshot.model)))
199
+ throw new SourceObsolete('frozen learning inputs changed');
200
+ return callback(content, feedback);
201
+ }));
202
+ }
203
+ scan = () => {
204
+ if (!this.active || this.scanning)
205
+ return;
206
+ this.scanning = true;
207
+ try {
208
+ this.lastError = null;
209
+ this.owner();
210
+ // Lost acknowledgements reconcile before admitting more paid work.
211
+ for (const job of this.store.pending(this.lane))
212
+ if (job.state === 'unknown') {
213
+ if (job.request)
214
+ this.reconcile(job);
215
+ // A durable request is always saved before any review/adoption call.
216
+ // No request proves that no memory mutation was dispatched. The paid
217
+ // extraction attempt remains consumed and is never retried.
218
+ else
219
+ this.store.settle(job.id, 'failed', 'interrupted-extraction-no-mutation-dispatched');
220
+ }
221
+ if (!this.available()) {
222
+ this.lastError = 'learning-authority-or-budget-unavailable';
223
+ return;
224
+ }
225
+ const owner = this.config.owner;
226
+ let stopped = false;
227
+ for (let pageNumber = 0; pageNumber < 10 && !stopped; pageNumber++) {
228
+ const after = this.store.cursor(this.lane, 'delivery');
229
+ const page = this.ports.delivery.listOwnerForegroundTaskSources({ ...this.scope(), ...(after ? { after } : {}), limit: 100 });
230
+ for (const source of page.items) {
231
+ const now = Date.now(), at = source.execution.completedAt ?? 0;
232
+ const eligible = source.execution.status === 'succeeded' && source.execution.quiescent && at <= now
233
+ && now - at <= this.config.lookbackMs && source.execution.modelSelectionState === 'frozen';
234
+ const intent = eligible ? { configDigest: this.configDigest, owner, kind: 'fact', subject: source.inboxId,
235
+ inboxId: source.inboxId, expectedSourceDigest: source.sourceDigest, createdAt: now,
236
+ expiresAt: Math.min(this.config.expiresAt, now + this.config.lookbackMs) } : undefined;
237
+ if (!this.store.stage({ lane: this.lane, feed: 'delivery', cursor: { ...page.nextCursor, sequence: source.completionSequence },
238
+ sequence: source.completionSequence, subject: source.inboxId, ...(intent ? { intent } : {}), maxPending: this.config.maxPending })) {
239
+ stopped = true;
240
+ break;
241
+ }
242
+ }
243
+ if (!stopped)
244
+ this.store.stage({ lane: this.lane, feed: 'delivery', cursor: page.nextCursor, sequence: page.nextCursor.sequence,
245
+ subject: 'cursor:delivery', maxPending: this.config.maxPending });
246
+ if (!page.hasMore)
247
+ break;
248
+ }
249
+ const scope = this.ports.evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
250
+ stopped = false;
251
+ for (let pageNumber = 0; pageNumber < 10 && !stopped; pageNumber++) {
252
+ const after = this.store.cursor(this.lane, 'evaluation');
253
+ const page = this.ports.evaluation.listTrustedTaskLearningProjections({ scope, ...(after ? { after } : {}), limit: 100 });
254
+ for (const item of page.items) {
255
+ const canonical = item.receipt, now = Date.now(), at = canonical.objective?.occurredAt ?? 0;
256
+ const foreground = canonical.projection.subjectKind === 'foreground-turn';
257
+ const status = canonical.objective?.status;
258
+ const eligible = foreground && canonical.projection.disposition === 'upsert' && (status === 'achieved' || status === 'not-achieved')
259
+ && at <= now && now - at <= this.config.lookbackMs;
260
+ const intent = eligible ? { configDigest: this.configDigest, owner, kind: 'experience',
261
+ subject: canonical.projection.subjectRef, inboxId: canonical.projection.subjectRef,
262
+ canonical: { outcomeId: canonical.triggerOutcomeId, version: canonical.projection.version,
263
+ digest: canonical.projection.digest, objectiveStatus: status }, createdAt: now,
264
+ expiresAt: Math.min(this.config.expiresAt, now + this.config.lookbackMs) } : undefined;
265
+ // No Delivery or Memory calls under this Evaluation lock.
266
+ const result = this.ports.evaluation.withTrustedCanonicalScopeWriterFence({ scope, scopeWatermark: page.scopeWatermark }, () => this.store.stage({ lane: this.lane, feed: 'evaluation', cursor: { scopeKey: page.nextCursor.scopeKey, watermark: item.watermark },
267
+ sequence: item.watermark, subject: canonical.projection.subjectRef, ...(intent ? { intent } : {}),
268
+ maxPending: this.config.maxPending, supersede: foreground }));
269
+ if (!result.matched || !result.value) {
270
+ stopped = true;
271
+ break;
272
+ }
273
+ }
274
+ if (!page.hasMore)
275
+ break;
276
+ }
277
+ for (const pending of this.store.pending(this.lane)) {
278
+ if (pending.state !== 'pending' && pending.state !== 'queued')
279
+ continue;
280
+ try {
281
+ let job = pending;
282
+ if (job.state === 'pending') {
283
+ // Each API owns its own producer locks; do not nest target discovery.
284
+ const targets = this.ports.memory.listLearningTargets({ owner, limit: 20 });
285
+ job = this.withSource(job, (content, feedback) => this.store.freeze(job.id, {
286
+ model: validateLearningModel(this.config.model ?? content.source.execution.modelSelection),
287
+ source: { inboxId: content.source.inboxId, sourceDigest: content.sourceDigest, contentDigest: content.contentDigest,
288
+ ...(job.intent.canonical ? { canonical: job.intent.canonical } : {}) },
289
+ ownerStatement: content.input.text, assistantReply: content.reply.text,
290
+ ...(feedback === undefined ? {} : { ownerFeedback: feedback }), targets,
291
+ }));
292
+ }
293
+ this.withSource(job, () => this.schedule(job));
294
+ }
295
+ catch (error) {
296
+ if (error instanceof SourceObsolete)
297
+ this.store.settle(pending.id, 'superseded', reasonText(error.message));
298
+ // Missing source/provider/scheduling readiness keeps the same durable intent.
299
+ else if (!(error instanceof SourcePending))
300
+ this.lastError = String(error).slice(0, 200);
301
+ }
302
+ }
303
+ this.lastScanAt = Date.now();
304
+ if (this.lastError === 'learning-authority-or-budget-unavailable')
305
+ this.lastError = null;
306
+ }
307
+ catch (error) {
308
+ this.lastError = String(error).slice(0, 200);
309
+ }
310
+ finally {
311
+ this.scanning = false;
312
+ }
313
+ };
314
+ definition(job) {
315
+ const owner = this.config.owner;
316
+ return { name: job ? 'Learn from an ordinary owner task' : 'Discover ordinary owner learning',
317
+ schedule: job ? { kind: 'at', at: new Date(job.intent.createdAt + 1000).toISOString() }
318
+ : { kind: 'cron', expression: '* * * * *', timezone: 'UTC' }, workspace: owner.workspace, agentPreset: owner.agentPreset,
319
+ timeoutMs: job ? this.config.timeoutMs + 310_000 : 30_000, misfire: { kind: 'latest' }, overlap: 'skip',
320
+ retrySafety: 'never', maxRetries: 0, principal: owner.principalId,
321
+ budgetId: job ? this.config.budgetId : this.config.scanBudgetId,
322
+ budgetAmount: job ? this.config.budgetAmount : this.config.scanBudgetAmount,
323
+ execution: { kind: 'host', executorId: EXECUTOR, executorContractVersion: 1, runbookId: job ? 'learn' : 'scan',
324
+ runbookVersion: 1, catalogDigest: CATALOG, targetScope: { workspace: owner.workspace, preset: owner.agentPreset },
325
+ scopeDigest: growthObjectDigest([owner.workspace, owner.agentPreset]), ownerRouteId: owner.authorityId,
326
+ activationNonce: job?.digest ?? this.configDigest } };
327
+ }
328
+ schedule(input) {
329
+ let job = input;
330
+ const definition = this.definition(job);
331
+ if (job.definitionHash === null) {
332
+ this.ports.automations.reconcileSystem({ owner: OWNER, automationId: job.id,
333
+ idempotencyKey: `${job.id}:prepare`, desiredStatus: 'paused', definition });
334
+ const receipt = this.ports.automations.inspectSystemOwnedActivation({ owner: OWNER, automationId: job.id });
335
+ if (!receipt || receipt.activationNonce !== job.digest || receipt.ownerRouteId !== this.config.owner.authorityId)
336
+ throw new Error('learning registration mismatch');
337
+ job = this.store.bind(job.id, receipt.definitionHash);
338
+ }
339
+ const receipt = this.ports.automations.inspectSystemOwnedActivation({ owner: OWNER, automationId: job.id });
340
+ if (!receipt || receipt.definitionHash !== job.definitionHash || receipt.activationNonce !== job.digest)
341
+ throw new Error('learning definition changed');
342
+ const terminal = this.ports.automations.inspectSystemOwned({ owner: OWNER, automationId: job.id }).latestTerminalRuns.production;
343
+ if (terminal && terminal.immutableContext.state === 'verified' && terminal.immutableContext.definitionHash === job.definitionHash) {
344
+ this.store.settle(job.id, 'failed', 'native-run-ended-before-claim');
345
+ return;
346
+ }
347
+ this.ports.automations.reconcileSystem({ owner: OWNER, automationId: job.id,
348
+ idempotencyKey: `${job.id}:activate`, desiredStatus: 'active', definition });
349
+ }
350
+ reviewMatches(job, receipt) {
351
+ return receipt.operationId === job.request.operationId && receipt.requestDigest === memoryLearningRequestDigest(job.request)
352
+ && receipt.authorityId === this.config.reviewAuthorityId && receipt.authorityDigest === this.config.reviewAuthorityDigest
353
+ && receipt.sessionId !== job.request.extractionSessionId;
354
+ }
355
+ reconcile(job) {
356
+ try {
357
+ const prior = this.ports.memory.lookupLearningAdoption({ request: job.request });
358
+ if (prior) {
359
+ this.store.settle(job.id, 'adopted', 'reconciled-original-adoption', prior.receiptDigest);
360
+ return;
361
+ }
362
+ this.withSource(job, () => undefined);
363
+ const review = this.ports.verifier.lookupMemoryLearningReview(job.request);
364
+ if (!review || !this.reviewMatches(job, review) || review.status === 'unknown')
365
+ return;
366
+ if (review.status === 'rejected')
367
+ this.store.settle(job.id, 'rejected', reasonText(review.reason), review.receiptDigest);
368
+ else {
369
+ // Lookup completed outside producer locks. Memory owns its adoption locks.
370
+ this.withSource(job, () => undefined);
371
+ const adopted = this.ports.memory.adoptReviewedLearning({ request: job.request });
372
+ this.store.settle(job.id, 'adopted', 'reconciled-approved-adoption', adopted.receiptDigest);
373
+ }
374
+ }
375
+ catch (error) {
376
+ if (error instanceof SourceObsolete)
377
+ this.store.settle(job.id, 'superseded', reasonText(error.message));
378
+ // Temporary absence retains the same request; paid calls are not replayed.
379
+ }
380
+ }
381
+ async execute(input) {
382
+ const result = (outcome) => ({ outcome,
383
+ failureClass: outcome === 'succeeded' ? 'none' : outcome === 'unknown' ? 'unknown' : 'configuration',
384
+ failurePhase: outcome === 'succeeded' ? 'none' : 'host-execution', failureCode: outcome === 'succeeded' ? 'none' : 'memory-learning-incomplete',
385
+ sideEffectState: outcome === 'succeeded' ? 'possible' : outcome === 'unknown' ? 'unknown' : 'none',
386
+ retryability: outcome === 'failed' ? 'after-intervention' : 'unsafe' });
387
+ let claimed;
388
+ try {
389
+ this.owner();
390
+ this.abort.signal.throwIfAborted();
391
+ input.signal.throwIfAborted();
392
+ const owner = this.config.owner;
393
+ if (input.executionMode !== 'production' || input.catalogDigest !== CATALOG || input.ownerRouteId !== owner.authorityId
394
+ || input.principal !== owner.principalId || input.targetScope.workspace !== owner.workspace
395
+ || input.targetScope.preset !== owner.agentPreset)
396
+ throw new Error('learning dispatch scope mismatch');
397
+ const activation = this.ports.automations.inspectSystemOwnedActivation({ owner: OWNER, automationId: input.automationId });
398
+ if (!activation || activation.definitionHash !== input.definitionHash || activation.activationNonce !== input.activationNonce)
399
+ throw new Error('learning activation changed');
400
+ if (input.automationId === this.scanId) {
401
+ if (input.activationNonce !== this.configDigest)
402
+ throw new Error('learning scanner configuration changed');
403
+ this.scan();
404
+ return result(this.lastError === null ? 'succeeded' : 'failed');
405
+ }
406
+ const queued = this.store.get(input.automationId);
407
+ if (!queued || queued.state !== 'queued' || !queued.snapshot || queued.digest !== input.activationNonce
408
+ || queued.definitionHash !== input.definitionHash || !this.available())
409
+ throw new Error('learning job is not admissible');
410
+ this.withSource(queued, () => { claimed = this.store.claim(queued.id, input.definitionHash, input.occurrenceId); });
411
+ const job = claimed;
412
+ const signal = AbortSignal.any([input.signal, this.abort.signal, AbortSignal.timeout(Math.max(1, Math.min(this.config.timeoutMs + 310_000, job.intent.expiresAt - Date.now(), this.config.expiresAt - Date.now())))]);
413
+ const assertCurrent = () => {
414
+ signal.throwIfAborted();
415
+ const current = this.store.get(job.id);
416
+ if (!current || current.state !== 'running' || current.occurrenceId !== input.occurrenceId)
417
+ throw new Error('learning claim superseded');
418
+ this.withSource(job, () => undefined);
419
+ };
420
+ assertCurrent();
421
+ this.policy(true, `memory-extract:${this.config.authorityId}:${job.id}`);
422
+ const extractionSignal = AbortSignal.any([signal, AbortSignal.timeout(this.config.timeoutMs)]);
423
+ const extraction = await this.ports.extract({ config: this.config, job, signal: extractionSignal, assertCurrent });
424
+ assertCurrent();
425
+ if (!extraction.proposal) {
426
+ this.store.settle(job.id, 'noop', reasonText(extraction.reason), extraction.outputDigest, input.occurrenceId);
427
+ return result('succeeded');
428
+ }
429
+ const proposal = extraction.proposal;
430
+ const quoteSources = [job.snapshot.ownerStatement, job.snapshot.ownerFeedback ?? ''];
431
+ if (!quoteSources.some(text => text.includes(proposal.evidenceQuote)))
432
+ throw new Error('learning quotation is not an owner source');
433
+ if (proposal.mutation.op !== 'remove' && proposal.mutation.entry.kind !== job.intent.kind)
434
+ throw new Error('learning kind changed');
435
+ const mutation = proposal.mutation;
436
+ if (mutation.op !== 'add' && !job.snapshot.targets.some(target => target.id === mutation.id && target.version === mutation.expectedVersion))
437
+ throw new Error('learning target not in frozen managed set');
438
+ const request = validateMemoryLearningReviewRequest({ protocol: 'memory-learning-review/v1',
439
+ operationId: job.id, extractionSessionId: extraction.sessionId,
440
+ owner, source: job.snapshot.source, ...proposal });
441
+ const saved = this.store.saveRequest(job.id, input.occurrenceId, request);
442
+ const review = await this.ports.verifier.reviewMemoryLearning(request, signal);
443
+ assertCurrent();
444
+ if (!this.reviewMatches(saved, review) || review.status === 'unknown') {
445
+ this.store.settle(job.id, 'unknown', 'independent-review-unresolved', undefined, input.occurrenceId);
446
+ return result('unknown');
447
+ }
448
+ if (review.status === 'rejected') {
449
+ this.store.settle(job.id, 'rejected', reasonText(review.reason), review.receiptDigest, input.occurrenceId);
450
+ return result('succeeded');
451
+ }
452
+ assertCurrent();
453
+ const adopted = this.ports.memory.adoptReviewedLearning({ request });
454
+ this.store.settle(job.id, 'adopted', 'independently-reviewed-and-adopted', adopted.receiptDigest, input.occurrenceId);
455
+ return result('succeeded');
456
+ }
457
+ catch (error) {
458
+ if (!this.active || this.abort.signal.aborted)
459
+ return result(claimed ? 'unknown' : 'failed');
460
+ if (claimed) {
461
+ const current = this.store.get(claimed.id);
462
+ if (current?.state === 'running' && current.occurrenceId === input.occurrenceId) {
463
+ this.store.settle(claimed.id, current.request ? 'unknown' : 'failed', reasonText(error), undefined, input.occurrenceId);
464
+ }
465
+ }
466
+ return result(claimed ? 'unknown' : 'failed');
467
+ }
468
+ finally {
469
+ if (this.active)
470
+ this.scan();
471
+ }
472
+ }
473
+ }
474
+ //# sourceMappingURL=runtime.js.map