@dsh-enhanced/assistant-skills 0.1.30

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/capture-expansion.d.ts +18 -0
  5. package/lib/capture-expansion.d.ts.map +1 -0
  6. package/lib/capture-expansion.js +187 -0
  7. package/lib/capture-expansion.js.map +1 -0
  8. package/lib/comparison.d.ts +69 -0
  9. package/lib/comparison.d.ts.map +1 -0
  10. package/lib/comparison.js +173 -0
  11. package/lib/comparison.js.map +1 -0
  12. package/lib/definition.d.ts +163 -0
  13. package/lib/definition.d.ts.map +1 -0
  14. package/lib/definition.js +450 -0
  15. package/lib/definition.js.map +1 -0
  16. package/lib/external-holdout.d.ts +34 -0
  17. package/lib/external-holdout.d.ts.map +1 -0
  18. package/lib/external-holdout.js +212 -0
  19. package/lib/external-holdout.js.map +1 -0
  20. package/lib/holdout-authority.d.ts +123 -0
  21. package/lib/holdout-authority.d.ts.map +1 -0
  22. package/lib/holdout-authority.js +263 -0
  23. package/lib/holdout-authority.js.map +1 -0
  24. package/lib/holdout-cli.d.ts +4 -0
  25. package/lib/holdout-cli.d.ts.map +1 -0
  26. package/lib/holdout-cli.js +221 -0
  27. package/lib/holdout-cli.js.map +1 -0
  28. package/lib/holdout-qualification.d.ts +83 -0
  29. package/lib/holdout-qualification.d.ts.map +1 -0
  30. package/lib/holdout-qualification.js +306 -0
  31. package/lib/holdout-qualification.js.map +1 -0
  32. package/lib/index.d.ts +22 -0
  33. package/lib/index.d.ts.map +1 -0
  34. package/lib/index.js +9 -0
  35. package/lib/index.js.map +1 -0
  36. package/lib/prospective-holdout.d.ts +35 -0
  37. package/lib/prospective-holdout.d.ts.map +1 -0
  38. package/lib/prospective-holdout.js +254 -0
  39. package/lib/prospective-holdout.js.map +1 -0
  40. package/lib/repair-admission.d.ts +11 -0
  41. package/lib/repair-admission.d.ts.map +1 -0
  42. package/lib/repair-admission.js +25 -0
  43. package/lib/repair-admission.js.map +1 -0
  44. package/lib/repair-agent.d.ts +44 -0
  45. package/lib/repair-agent.d.ts.map +1 -0
  46. package/lib/repair-agent.js +241 -0
  47. package/lib/repair-agent.js.map +1 -0
  48. package/lib/repair-feedback.d.ts +70 -0
  49. package/lib/repair-feedback.d.ts.map +1 -0
  50. package/lib/repair-feedback.js +79 -0
  51. package/lib/repair-feedback.js.map +1 -0
  52. package/lib/repair-profile.d.ts +27 -0
  53. package/lib/repair-profile.d.ts.map +1 -0
  54. package/lib/repair-profile.js +51 -0
  55. package/lib/repair-profile.js.map +1 -0
  56. package/lib/repair-runtime.d.ts +35 -0
  57. package/lib/repair-runtime.d.ts.map +1 -0
  58. package/lib/repair-runtime.js +181 -0
  59. package/lib/repair-runtime.js.map +1 -0
  60. package/lib/replay.d.ts +38 -0
  61. package/lib/replay.d.ts.map +1 -0
  62. package/lib/replay.js +240 -0
  63. package/lib/replay.js.map +1 -0
  64. package/lib/sealed-holdout.d.ts +20 -0
  65. package/lib/sealed-holdout.d.ts.map +1 -0
  66. package/lib/sealed-holdout.js +12 -0
  67. package/lib/sealed-holdout.js.map +1 -0
  68. package/lib/service.d.ts +968 -0
  69. package/lib/service.d.ts.map +1 -0
  70. package/lib/service.js +1929 -0
  71. package/lib/service.js.map +1 -0
  72. package/lib/store.d.ts +366 -0
  73. package/lib/store.d.ts.map +1 -0
  74. package/lib/store.js +1517 -0
  75. package/lib/store.js.map +1 -0
  76. package/lib/version.d.ts +2 -0
  77. package/lib/version.d.ts.map +1 -0
  78. package/lib/version.js +2 -0
  79. package/lib/version.js.map +1 -0
  80. package/lib/watch-proof.d.ts +30 -0
  81. package/lib/watch-proof.d.ts.map +1 -0
  82. package/lib/watch-proof.js +209 -0
  83. package/lib/watch-proof.js.map +1 -0
  84. package/package.json +141 -0
package/lib/service.js ADDED
@@ -0,0 +1,1929 @@
1
+ import { Context, Service } from '@deepseek-ai/cordis';
2
+ import { ToolCallId } from '@deepseek-ai/dsh-llm/brand';
3
+ import { defineTool } from '@deepseek-ai/dsh-tools';
4
+ import { acceptanceDigest } from '@dsh-enhanced/task-acceptance-contract';
5
+ import Schema from '@deepseek-ai/schemastery';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { createDefinition, fileObservationSteps, instantiate } from './definition.js';
9
+ import { captureFailureCandidateProvenance, captureRunExpansions } from './capture-expansion.js';
10
+ import { validateComparisonProfiles, SkillComparator } from './comparison.js';
11
+ import { watchBindingCurrent, watchObservation, watchObservationResult, watchObservationRevision } from './watch-proof.js';
12
+ import { sealedPlan } from './sealed-holdout.js';
13
+ import { openHoldoutProcess, validateExternalHoldoutProfiles } from './external-holdout.js';
14
+ import { canaryAdmissionMatches, inspectProspectiveQualification, qualifyHoldout } from './holdout-qualification.js';
15
+ import { materializeCanaryAdmission } from './repair-admission.js';
16
+ import { validateRepairProfiles } from './repair-profile.js';
17
+ import { RepairContinuationRuntime } from './repair-runtime.js';
18
+ import { OwnerRepairAgentRuntime } from './repair-agent.js';
19
+ import { enqueueRepairFeedback } from './repair-feedback.js';
20
+ import { SkillStore } from './store.js';
21
+ export const Config = Schema.object({
22
+ databasePath: Schema.string().default(join(homedir(), '.dsh', 'assistant-skills.sqlite')),
23
+ allowedTools: Schema.array(Schema.string()).default(['read', 'write', 'edit']),
24
+ comparisons: Schema.array(Schema.any()).default([]),
25
+ externalHoldouts: Schema.array(Schema.any()).default([]),
26
+ repairProfiles: Schema.array(Schema.any()).default([]),
27
+ candidateTtlMs: Schema.number().step(1).min(1000).max(604800000).default(86400000),
28
+ maxDurationMs: Schema.number().step(1).min(1000).max(300000).default(60000),
29
+ });
30
+ const output = { schema: { type: 'object', additionalProperties: false, properties: { context: { type: 'string', required: true } } },
31
+ render: (_args, value) => [{ type: 'text', text: value.context }] };
32
+ const businessGoalId = 'Business Goal ID returned by goal_create or goal_context (the goal_id label), not the native get_goal ID.';
33
+ const resource = { kind: 'evolution', id: 'verified-workflows' };
34
+ function parse(value, array = false) {
35
+ if (typeof value !== 'string' || Buffer.byteLength(value) > 262144)
36
+ throw new Error('assistant-skills: bounded JSON required');
37
+ const parsed = JSON.parse(value);
38
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) !== array)
39
+ throw new Error('assistant-skills: invalid JSON shape');
40
+ return parsed;
41
+ }
42
+ function plainArguments(value) {
43
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype || Object.getOwnPropertySymbols(value).length !== 0)
44
+ return undefined;
45
+ const descriptors = Object.getOwnPropertyDescriptors(value);
46
+ if (!Object.values(descriptors).every(descriptor => descriptor.enumerable && 'value' in descriptor))
47
+ return undefined;
48
+ return value;
49
+ }
50
+ function exactArguments(value, allowed) {
51
+ const args = plainArguments(value);
52
+ if (!args || Object.keys(args).some(key => !allowed.includes(key)))
53
+ return undefined;
54
+ return args;
55
+ }
56
+ function failureLocator(value) {
57
+ const locator = plainArguments(value);
58
+ return locator !== undefined && Object.keys(locator).length === 2 && Object.hasOwn(locator, 'sessionId') && Object.hasOwn(locator, 'goalId')
59
+ && [locator.sessionId, locator.goalId].every(part => typeof part === 'string' && part.length > 0 && part.length <= 4_096);
60
+ }
61
+ function failureWindow(input) {
62
+ const legacyPresent = input.triggerGoalId !== undefined || input.triggerSessionId !== undefined;
63
+ if (input.failureLocators !== undefined && legacyPresent)
64
+ throw new Error('assistant-skills: choose legacy failure locator or failure locator list');
65
+ const rawFailures = input.failureLocators === undefined
66
+ ? [{ sessionId: input.triggerSessionId, goalId: input.triggerGoalId }]
67
+ : input.failureLocators;
68
+ const minimumOccurrences = input.minimumOccurrences ?? 1;
69
+ if (!Array.isArray(rawFailures) || Object.getPrototypeOf(rawFailures) !== Array.prototype || rawFailures.length < 1 || rawFailures.length > 32
70
+ || Object.getOwnPropertySymbols(rawFailures).length !== 0 || Reflect.ownKeys(rawFailures).length !== rawFailures.length + 1
71
+ || !Number.isSafeInteger(minimumOccurrences) || minimumOccurrences < 1 || minimumOccurrences > 32
72
+ || minimumOccurrences > rawFailures.length) {
73
+ throw new Error('assistant-skills: invalid bounded failure locator window');
74
+ }
75
+ const descriptors = Object.getOwnPropertyDescriptors(rawFailures), failures = [];
76
+ for (let index = 0; index < rawFailures.length; index++) {
77
+ const descriptor = descriptors[String(index)];
78
+ if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor) || !failureLocator(descriptor.value)) {
79
+ throw new Error('assistant-skills: invalid bounded failure locator window');
80
+ }
81
+ failures.push(descriptor.value);
82
+ }
83
+ if (input.failureLocators === undefined && (minimumOccurrences !== 1 || !failureLocator(failures[0]))) {
84
+ throw new Error('assistant-skills: invalid bounded failure locator window');
85
+ }
86
+ const identities = failures.map(locator => acceptanceDigest(locator));
87
+ if (new Set(identities).size !== identities.length)
88
+ throw new Error('assistant-skills: duplicate failure locator');
89
+ const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
90
+ const canonical = failures.map(locator => ({ sessionId: locator.sessionId, goalId: locator.goalId }))
91
+ .sort((left, right) => compare(left.sessionId, right.sessionId) || compare(left.goalId, right.goalId));
92
+ return { failures: Object.freeze(canonical.map(locator => Object.freeze(locator))), minimumOccurrences: minimumOccurrences };
93
+ }
94
+ function publicDefinition(definition) {
95
+ const source = { digest: acceptanceDigest(definition.source), goalDefinitionDigest: definition.source.goal.definition.digest, stepCount: definition.source.steps.length,
96
+ failedObservationCount: definition.source.failedObservations?.length ?? 0, segmentCount: definition.source.segments?.length ?? 0 };
97
+ return { protocol: definition.protocol, name: definition.name, description: definition.description, source, inputs: definition.inputs, steps: definition.steps,
98
+ ...(definition.fileObservations === undefined ? {} : { fileObservations: definition.fileObservations }),
99
+ ...(definition.runExpansions === undefined ? {} : { runExpansionCount: definition.runExpansions.length, runExpansionsDigest: acceptanceDigest(definition.runExpansions) }),
100
+ preconditions: definition.preconditions, compensation: definition.compensation,
101
+ ...(!('version' in definition) ? {} : { version: definition.version, parentVersion: definition.parentVersion, retired: definition.retired, definitionDigest: acceptanceDigest(definition), createdAt: definition.createdAt, updatedAt: definition.updatedAt,
102
+ ...(definition.restoredFromVersion === undefined ? {} : { restoredFromVersion: definition.restoredFromVersion }) }) };
103
+ }
104
+ function publicWatch(watch) {
105
+ const achievedCount = watch.observations.filter(observation => observation.objectiveStatus === 'achieved').length;
106
+ const notAchievedCount = watch.observations.filter(observation => observation.objectiveStatus === 'not-achieved').length;
107
+ return { id: watch.id, skillName: watch.skillName, version: watch.version, definitionDigest: watch.definitionDigest, fallbackVersion: watch.fallbackVersion, fallbackDigest: watch.fallbackDigest, expiresAt: watch.expiresAt, maxRuns: watch.maxRuns, failureThreshold: watch.failureThreshold,
108
+ state: watch.state, observedRuns: watch.observations.length, achieved: achievedCount, notAchieved: notAchievedCount, createdAt: watch.createdAt, updatedAt: watch.updatedAt,
109
+ ...(watch.rollbackVersion === undefined ? {} : { rollbackVersion: watch.rollbackVersion }), ...(watch.taskFamily === undefined ? {} : { taskFamilyDigest: acceptanceDigest(watch.taskFamily) }) };
110
+ }
111
+ function publicCapture(capture) {
112
+ return { id: capture.id, name: capture.name, parentVersion: capture.parentVersion, expiresAt: capture.expiresAt, state: capture.state,
113
+ ...(capture.candidateId === undefined ? {} : { candidateId: capture.candidateId }) };
114
+ }
115
+ function publicDeployment(deployment) {
116
+ return { id: deployment.id, candidateId: deployment.candidateId, comparisonId: deployment.comparisonId, qualificationDigest: deployment.qualificationDigest, admissionDigest: deployment.admissionDigest, candidateDefinitionDigest: deployment.candidateDefinitionDigest,
117
+ skillName: deployment.skillName, version: deployment.version, definitionDigest: deployment.definitionDigest, parentVersion: deployment.parentVersion, watchId: deployment.watchId, taskFamilyDigest: acceptanceDigest(deployment.taskFamily), expiresAt: deployment.expiresAt, maxRuns: deployment.maxRuns, canaryRuns: deployment.canaryRuns,
118
+ runCount: deployment.runIds.length, state: deployment.state, createdAt: deployment.createdAt, updatedAt: deployment.updatedAt };
119
+ }
120
+ function publicCandidate(candidate, parent) {
121
+ const before = new Set(parent?.steps.map(step => step.toolName) ?? []);
122
+ const after = new Set(candidate.definition.steps.map(step => step.toolName));
123
+ const failure = candidate.failureProvenance;
124
+ return { id: candidate.id, definition: publicDefinition(candidate.definition), definitionDigest: acceptanceDigest(candidate.definition), parentVersion: candidate.parentVersion, parentDigest: candidate.parentDigest, reason: candidate.reason,
125
+ trigger: failure === undefined ? candidate.trigger : `host-verified-failure:${failure.trigger.failureCategory}`,
126
+ expiresAt: candidate.expiresAt, state: candidate.state, createdAt: candidate.createdAt, updatedAt: candidate.updatedAt,
127
+ ...(candidate.activatedVersion === undefined ? {} : { activatedVersion: candidate.activatedVersion }),
128
+ ...(candidate.activationComparisonId === undefined ? {} : { activationComparisonId: candidate.activationComparisonId }),
129
+ ...(candidate.deploymentId === undefined ? {} : { deploymentId: candidate.deploymentId }),
130
+ ...(candidate.activationWatchId === undefined ? {} : { activationWatchId: candidate.activationWatchId }),
131
+ ...(failure === undefined ? {} : { failure: {
132
+ // Keep the v1 projection stable for existing clients while adding the
133
+ // bounded repeated-failure aliases. Raw Goal, Session, run, contract,
134
+ // receipt and trace identities remain private in failureProvenance.
135
+ protocol: failure.protocol,
136
+ provenanceDigest: acceptanceDigest(failure),
137
+ category: failure.trigger.failureCategory,
138
+ occurrences: failure.trigger.failures.length,
139
+ taskFamilyId: failure.trigger.taskFamily.id,
140
+ taskFamilyDefinitionDigest: failure.trigger.taskFamily.definitionDigest,
141
+ permissionDelta: failure.permissionDelta,
142
+ rollbackTarget: failure.rollbackTarget,
143
+ count: failure.trigger.failures.length,
144
+ digest: acceptanceDigest(failure),
145
+ } }),
146
+ comparison: { kind: 'structural-only', improvement: 'unmeasured',
147
+ toolsAdded: [...after].filter(tool => !before.has(tool)), toolsRemoved: [...before].filter(tool => !after.has(tool)),
148
+ inputsChanged: acceptanceDigest(parent?.inputs ?? []) !== acceptanceDigest(candidate.definition.inputs),
149
+ changedSteps: Array.from({ length: Math.max(parent?.steps.length ?? 0, candidate.definition.steps.length) }, (_, index) => ({ index,
150
+ before: parent?.steps[index] ? acceptanceDigest(parent.steps[index]) : null,
151
+ after: candidate.definition.steps[index] ? acceptanceDigest(candidate.definition.steps[index]) : null })).filter(step => step.before !== step.after) } };
152
+ }
153
+ function trialProofSteps(steps, candidateId, trialRunId, goalId, invocationId, inputs) {
154
+ let trials = 0;
155
+ for (const step of steps) {
156
+ const args = plainArguments(step.arguments);
157
+ if (!args)
158
+ return false;
159
+ if (step.toolName === 'skill_trial') {
160
+ trials++;
161
+ const trial = exactArguments(args, ['candidate_id', 'goal_id', 'inputs_json', 'invocation_id']);
162
+ if (!trial || trials !== 1 || trial.candidate_id !== candidateId || trial.goal_id !== goalId || trial.invocation_id !== invocationId
163
+ || acceptanceDigest(parse(trial.inputs_json === undefined ? '{}' : trial.inputs_json)) !== acceptanceDigest(inputs))
164
+ return false;
165
+ continue;
166
+ }
167
+ if (step.toolName === 'get_goal' && exactArguments(args, []))
168
+ continue;
169
+ if (step.toolName === 'skill_candidates') {
170
+ const candidate = exactArguments(args, ['candidate_id']);
171
+ if (candidate && (Object.keys(candidate).length === 0 || candidate.candidate_id === candidateId))
172
+ continue;
173
+ }
174
+ if (step.toolName === 'skill_status') {
175
+ const status = exactArguments(args, ['run_id']);
176
+ if (status && (Object.keys(status).length === 0 || status.run_id === trialRunId))
177
+ continue;
178
+ }
179
+ if (step.toolName === 'goal_context') {
180
+ const context = exactArguments(args, ['goal_id', 'focus']);
181
+ if (context && (Object.keys(context).length === 0 || context.goal_id === goalId && (context.focus === undefined || context.focus === false)))
182
+ continue;
183
+ }
184
+ return false;
185
+ }
186
+ return trials === 1;
187
+ }
188
+ /** Fixed tool compositions run in the original native Goal; no new AgentLoop or scheduler. */
189
+ export class AssistantSkillsService extends Service {
190
+ static Config = Config;
191
+ #store;
192
+ #allowed;
193
+ #duration;
194
+ #candidateTtl;
195
+ #comparisons;
196
+ #externalHoldouts;
197
+ #repairProfiles;
198
+ #repairRuntime;
199
+ #repairAgents;
200
+ #repairTask;
201
+ #repairAssertions = new Map();
202
+ #repairStops = new Set();
203
+ #comparators = new Map();
204
+ #comparing = new Set();
205
+ #lifecycle = new AbortController();
206
+ #providers = new Set();
207
+ #sealedHoldout;
208
+ #sealedGeneration;
209
+ #reconcileQueued = false;
210
+ #reconcileDirty = false;
211
+ #captureInflight = new Set();
212
+ #captureDirty = new Set();
213
+ #captureTasks = new Set();
214
+ #deployedExecutions = new Set();
215
+ #active = true;
216
+ constructor(ctx, config = {}) {
217
+ super(ctx, 'assistantSkills');
218
+ this.#allowed = Object.freeze([...(config.allowedTools ?? ['read', 'write', 'edit'])]);
219
+ this.#duration = config.maxDurationMs ?? 60000;
220
+ this.#candidateTtl = config.candidateTtlMs ?? 86400000;
221
+ if (!Number.isSafeInteger(this.#duration) || this.#duration < 1000 || this.#duration > 300000
222
+ || !Number.isSafeInteger(this.#candidateTtl) || this.#candidateTtl < 1000 || this.#candidateTtl > 604800000
223
+ || this.#allowed.length > 32 || this.#allowed.some(name => typeof name !== 'string' || !/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/u.test(name)))
224
+ throw new Error('assistant-skills: invalid configuration');
225
+ this.#comparisons = validateComparisonProfiles(config.comparisons ?? []);
226
+ this.#externalHoldouts = validateExternalHoldoutProfiles(config.externalHoldouts ?? []);
227
+ this.#repairProfiles = validateRepairProfiles(config.repairProfiles ?? [], this.#externalHoldouts);
228
+ this.#store = new SkillStore(config.databasePath ?? join(homedir(), '.dsh', 'assistant-skills.sqlite'));
229
+ ctx.effect(() => async () => { this.#active = false; this.#lifecycle.abort(); await Promise.all([...this.#repairStops].map(stop => stop())); await Promise.allSettled(this.#comparing); await Promise.allSettled(this.#captureTasks); await Promise.allSettled([...this.#comparators.values()].map(async (value) => (await value).close())); this.#store.close(); }, 'assistant-skills.store');
230
+ if (this.#repairProfiles.length > 0)
231
+ this.#installRepairRuntime(ctx);
232
+ ctx.inject(['tools', 'agents', 'assistantGoals', 'assistantPolicy', 'assistantDelivery'], runtime => {
233
+ runtime.tools.register(defineTool({ name: 'skill_save', description: 'Save the exact successful tool trace of this owner session’s independently achieved Goal as a private versioned skill. Requires the current human request. Historical acceptance is provenance, never permission or acceptance for a future run.',
234
+ parameters: { goal_id: { type: 'string', required: true, description: businessGoalId }, name: { type: 'string', required: true }, description: { type: 'string', required: true }, bindings_json: { type: 'string', description: 'JSON array of {name,stepId,path}; path is a scalar argument JSON pointer. Empty array keeps the original arguments.' }, expected_version: { type: 'integer' } }, output,
235
+ execute: async (args, exec) => ({ context: JSON.stringify(this.save(exec.agent, args.goal_id, { name: args.name, description: args.description, bindings: parse(args.bindings_json ?? '[]', true) }, args.expected_version ?? 0)) }) }));
236
+ runtime.tools.register(defineTool({ name: 'skill_run', description: 'Replay a saved skill’s fixed tool steps in a fresh native Goal. If the current owner Goal is active but has no admitted native round, this returns awaiting-native-round with no steps or durable run, ends the turn, and the next native round must repeat the same invocation_id. Every nested call retains native permissions, approvals, cancellation and budgets. Tool success still requires fresh independent Goal acceptance.',
237
+ parameters: { goal_id: { type: 'string', required: true, description: businessGoalId }, name: { type: 'string', required: true }, version: { type: 'integer', required: true }, inputs_json: { type: 'string', description: 'JSON object with only declared typed input parameters.' }, invocation_id: { type: 'string', required: true } }, output,
238
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.run(exec, args.goal_id, args.name, args.version, parse(args.inputs_json ?? '{}'), args.invocation_id)) }) }));
239
+ runtime.tools.register(defineTool({ name: 'skill_status', description: 'Read this owner’s active saved skill definitions, typed inputs and source acceptance, or inspect a specific durable invocation. Success means steps executed, not Goal achievement.', parameters: { run_id: { type: 'string' } }, output,
240
+ execute: async (args, exec) => ({ context: JSON.stringify(this.inspect(exec.agent, args.run_id)) }) }));
241
+ runtime.tools.register(defineTool({ name: 'skill_retire', description: 'Retire the current saved skill version following the current authenticated owner request. Pending steps recheck retirement; past effects remain and require explicit repair.', parameters: { name: { type: 'string', required: true }, expected_version: { type: 'integer', required: true } }, output,
242
+ execute: async (args, exec) => ({ context: JSON.stringify(this.retire(exec.agent, args.name, args.expected_version)) }) }));
243
+ });
244
+ ctx.inject(['tools', 'agents', 'assistantGoals', 'assistantPolicy', 'assistantDelivery'], runtime => {
245
+ runtime.tools.register(defineTool({ name: 'skill_compare', description: 'Run one configured local comparison of this pending candidate and its current parent through native file tools and independent isolated artifact checks. Requires the current owner request and a finite local comparison profile. Stable invocation_id never repeats unknown work. Returns measured quality, never promotion permission.',
246
+ parameters: { candidate_id: { type: 'string', required: true }, profile_id: { type: 'string', required: true, description: 'Exact opaque id returned by skill_comparison_status with kind=local and executionTool=skill_compare. Query status first when the user did not supply this configured id; never infer it from task or version labels.' }, invocation_id: { type: 'string', required: true } }, output,
247
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.compare(exec, args.candidate_id, args.profile_id, args.invocation_id)) }) }));
248
+ runtime.tools.register(defineTool({ name: 'skill_qualify', description: 'Run one configured external independent holdout qualification for a pending candidate and its current parent. The authority keeps holdout questions and keys private. Requires the current owner request. A stable invocation_id never repeats an unknown or completed qualification. It returns measurement only and never activates or promotes a skill.',
249
+ parameters: { candidate_id: { type: 'string', required: true }, profile_id: { type: 'string', required: true, description: 'Exact opaque id returned by skill_comparison_status with kind=external and executionTool=skill_qualify. Query status first when the user did not supply this configured id; never infer it from dataset, generator, task, or version labels.' }, invocation_id: { type: 'string', required: true } }, output,
250
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.qualifyExternalHoldout(exec, args.candidate_id, args.profile_id, args.invocation_id)) }) }));
251
+ runtime.tools.register(defineTool({ name: 'skill_canary', description: 'Owner-authorize one finite canary: it starts one configured prospective external comparison for this exact candidate and parent, then deploys only if every qualification and authorization gate passes. It runs a finite exact-version watch, automatically promotes after fresh independent successes, and blocks then rolls back after a verified failure.',
252
+ parameters: { candidate_id: { type: 'string', required: true }, profile_id: { type: 'string', required: true, description: 'Exact opaque id returned by skill_comparison_status with canaryExecutionTool=skill_canary. Query status first when the user did not supply this configured id; never infer it from generator, task, or version labels.' }, invocation_id: { type: 'string', required: true }, owner_route_id: { type: 'string', required: true }, expires_at: { type: 'integer', required: true }, max_runs: { type: 'integer', required: true }, canary_runs: { type: 'integer', required: true } }, output,
253
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.canary(exec, args.candidate_id, args.profile_id, args.invocation_id, { ownerRouteId: args.owner_route_id, expiresAt: args.expires_at, maxRuns: args.max_runs, canaryRuns: args.canary_runs })) }) }));
254
+ runtime.tools.register(defineTool({ name: 'skill_comparison_status', description: 'Read redacted comparison identity, state, aggregate quality and public digests, or call without comparison_id to discover exact owner-scoped configured profile IDs and their execution tools before choosing a comparison entrypoint. Private cells, keys, observations, inputs and expected answers are never included.', parameters: { comparison_id: { type: 'string' } }, output,
255
+ execute: async (args, exec) => ({ context: JSON.stringify(this.comparisonStatus(exec.agent, args.comparison_id)) }) }));
256
+ runtime.tools.register(defineTool({ name: 'skill_candidate', description: 'Draft a private candidate from an independently achieved Goal following the current owner request. The current active skill stays unchanged. Review the stored trace and structural delta; no performance gain is inferred.',
257
+ parameters: { goal_id: { type: 'string', required: true, description: businessGoalId }, name: { type: 'string', required: true }, description: { type: 'string', required: true }, bindings_json: { type: 'string' }, parent_version: { type: 'integer', required: true }, reason: { type: 'string', required: true }, trigger: { type: 'string', required: true } }, output,
258
+ execute: async (args, exec) => ({ context: JSON.stringify(this.stage(exec.agent, args.goal_id, { name: args.name, description: args.description, bindings: parse(args.bindings_json ?? '[]', true) }, args.parent_version, args.reason, args.trigger)) }) }));
259
+ runtime.tools.register(defineTool({ name: 'skill_failure_candidate', description: 'Draft a private repair candidate from one or more exact independently verified not-achieved Goals and a later independent achieved repair Goal. Use failure_locators plus minimum_occurrences for repeated failures; the legacy trigger Goal fields remain valid for one failure. Failure outcomes, summaries, provenance and digests are read only from the current Goals Host capability and are never caller inputs. The current active skill stays unchanged.',
260
+ parameters: { owner_route_id: { type: 'string', required: true }, trigger_goal_id: { type: 'string', description: `${businessGoalId} Legacy single-failure input; omit when failure_locators is supplied.` }, trigger_session_id: { type: 'string', description: 'Legacy single-failure session; omit when failure_locators is supplied.' }, failure_locators: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { session_id: { type: 'string', required: true }, goal_id: { type: 'string', required: true, description: businessGoalId } } }, description: 'Bounded list of 1-32 exact failure locators. It contains identities only, never outcomes, receipts, provenance or digests.' }, minimum_occurrences: { type: 'integer', description: 'Required evidence count for failure_locators (1-32 and no greater than the locator count). Legacy single-failure input defaults to 1.' }, repair_goal_id: { type: 'string', required: true, description: businessGoalId }, repair_session_id: { type: 'string', required: true }, task_family_id: { type: 'string', required: true }, name: { type: 'string', required: true }, description: { type: 'string', required: true }, bindings_json: { type: 'string' }, parent_version: { type: 'integer', required: true } }, output,
261
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.stageFailureCandidate(exec, { ownerRouteId: args.owner_route_id, ...(args.trigger_goal_id === undefined ? {} : { triggerGoalId: args.trigger_goal_id }), ...(args.trigger_session_id === undefined ? {} : { triggerSessionId: args.trigger_session_id }), ...(args.failure_locators === undefined ? {} : { failureLocators: args.failure_locators.map(locator => ({ sessionId: locator.session_id, goalId: locator.goal_id })) }), ...(args.minimum_occurrences === undefined ? {} : { minimumOccurrences: args.minimum_occurrences }), repairGoalId: args.repair_goal_id, repairSessionId: args.repair_session_id, taskFamilyId: args.task_family_id, name: args.name, description: args.description, bindings: parse(args.bindings_json ?? '[]', true), parentVersion: args.parent_version })) }) }));
262
+ runtime.tools.register(defineTool({ name: 'skill_candidates', description: 'Inspect private candidate definitions, expiry, structural differences and trial references. Pending candidates are not active native skills.', parameters: { candidate_id: { type: 'string' } }, output,
263
+ execute: async (args, exec) => ({ context: JSON.stringify(this.candidates(exec.agent, args.candidate_id)) }) }));
264
+ runtime.tools.register(defineTool({ name: 'skill_trial', description: 'Run a pending candidate in a fresh native Goal using ordinary tool permissions and budgets. If the current owner Goal has no admitted native round, it returns awaiting-native-round without steps or durable work and the next native round must repeat the same invocation_id. This can have real effects once admitted and never changes the active skill. Independent Goal acceptance and a current owner request are required for later activation.',
265
+ parameters: { candidate_id: { type: 'string', required: true }, goal_id: { type: 'string', required: true, description: businessGoalId }, inputs_json: { type: 'string' }, invocation_id: { type: 'string', required: true } }, output,
266
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.trial(exec, args.candidate_id, args.goal_id, parse(args.inputs_json ?? '{}'), args.invocation_id)) }) }));
267
+ runtime.tools.register(defineTool({ name: 'skill_activate', description: 'Activate a candidate following the current owner request only after a fresh independently accepted Goal has one exact successful skill_trial as its sole business execution. The accepted round may also contain only validated read-only metadata inspection. This is owner-approved activation, not automatic promotion or proof of improvement.',
268
+ parameters: { candidate_id: { type: 'string', required: true }, trial_run_id: { type: 'string', required: true } }, output,
269
+ execute: async (args, exec) => ({ context: JSON.stringify(this.activate(exec.agent, args.candidate_id, args.trial_run_id)) }) }));
270
+ runtime.tools.register(defineTool({ name: 'skill_activate_watched', description: 'Following the current owner request, activate an independently accepted candidate and register a finite exact-version observation watch in one atomic commit. Requires an existing parent and current activation and watch authority. Failure leaves the parent active. Because this public flow has no operator-pinned task family, achieved and not-achieved observations never promote or roll back the active version.',
271
+ parameters: { candidate_id: { type: 'string', required: true }, trial_run_id: { type: 'string', required: true }, owner_route_id: { type: 'string', required: true }, expires_at: { type: 'integer', required: true }, max_runs: { type: 'integer', required: true }, failure_threshold: { type: 'integer', required: true } }, output,
272
+ execute: async (args, exec) => ({ context: JSON.stringify(this.activate(exec.agent, args.candidate_id, args.trial_run_id, { ownerRouteId: args.owner_route_id, expiresAt: args.expires_at, maxRuns: args.max_runs, failureThreshold: args.failure_threshold })) }) }));
273
+ runtime.tools.register(defineTool({ name: 'skill_reject', description: 'Reject a pending candidate following the current owner request; the active skill stays unchanged.', parameters: { candidate_id: { type: 'string', required: true } }, output,
274
+ execute: async (args, exec) => ({ context: JSON.stringify(this.reject(exec.agent, args.candidate_id)) }) }));
275
+ runtime.tools.register(defineTool({ name: 'skill_rollback', description: 'Restore the current skill’s immediate parent as a new immutable version following the current owner request. Historical runs and effects remain recorded.',
276
+ parameters: { name: { type: 'string', required: true }, expected_version: { type: 'integer', required: true }, target_version: { type: 'integer', required: true } }, output,
277
+ execute: async (args, exec) => ({ context: JSON.stringify(this.rollback(exec.agent, args.name, args.expected_version, args.target_version)) }) }));
278
+ runtime.tools.register(defineTool({ name: 'skill_watch', description: 'Explicitly authorize a finite observation-only watch for one exact active skill version. It observes only later successful skill_run calls bound to independently verified native Goal outcomes. This public flow has no operator-pinned task family, so achieved and not-achieved observations never promote or roll back the active version.',
279
+ parameters: { owner_route_id: { type: 'string', required: true }, name: { type: 'string', required: true }, version: { type: 'integer', required: true }, fallback_version: { type: 'integer', required: true }, expires_at: { type: 'integer', required: true }, max_runs: { type: 'integer', required: true }, failure_threshold: { type: 'integer', required: true } }, output,
280
+ execute: async (args, exec) => ({ context: JSON.stringify(this.watch(exec.agent, { ownerRouteId: args.owner_route_id, skillName: args.name, version: args.version, fallbackVersion: args.fallback_version, expiresAt: args.expires_at, maxRuns: args.max_runs, failureThreshold: args.failure_threshold })) }) }));
281
+ runtime.tools.register(defineTool({ name: 'skill_capture', description: 'During the owner turn that created the current active Goal, preauthorize one finite automatic pending candidate after that Goal independently achieves and naturally ends. Supply the configured public owner_route_id, business Goal ID, name and expiry. Set start_native_rounds=true only when capture should immediately hand the Goal to Host-native execution and extraction; after successful registration the owner turn must not continue business work. Leave it false to continue composing authorized schedule or wait work. If registration fails, correct the ID and retry while still in the owner turn; failure is not authorization. It never activates, compares, runs, or expands a skill.',
282
+ parameters: { owner_route_id: { type: 'string', required: true }, goal_id: { type: 'string', required: true, description: businessGoalId }, name: { type: 'string', required: true }, description: { type: 'string', required: true }, parent_version: { type: 'integer', required: true }, expires_at: { type: 'integer', required: true }, start_native_rounds: { type: 'boolean', description: 'When explicitly true and registration succeeds, conclude this owner turn so the Host may start native Goal rounds.' } }, output,
283
+ execute: async (args, exec) => { const saved = this.capture(exec.agent, { ownerRouteId: args.owner_route_id, goalId: args.goal_id, name: args.name, description: args.description, parentVersion: args.parent_version, expiresAt: args.expires_at }); if (args.start_native_rounds === true)
284
+ exec.concludeTurn(); return { context: JSON.stringify(saved) }; } }));
285
+ runtime.tools.register(defineTool({ name: 'skill_captures', description: 'Inspect owner-preauthorized automatic capture records and their pending, captured, revoked, expired, unsupported, or unknown terminal state.', parameters: {}, output,
286
+ execute: async (_args, exec) => ({ context: JSON.stringify(this.#store.listCaptures(this.#scope(exec.agent, 'inspect')).map(publicCapture)) }) }));
287
+ runtime.tools.register(defineTool({ name: 'skill_watches', description: 'Inspect this owner’s finite watch lifecycle, exact versions and aggregate run/outcome counts. Raw run IDs, receipts, observations and authority material are never included. Only qualified canary watches with an operator-pinned task family can authorize automatic promotion or rollback; public standalone watches are observation-only.', parameters: {}, output,
288
+ execute: async (_args, exec) => ({ context: JSON.stringify(this.#store.listWatches(this.#scope(exec.agent, 'inspect')).map(publicWatch)) }) }));
289
+ runtime.tools.register(defineTool({ name: 'skill_deployment_status', description: 'Inspect this owner’s finite canary deployment lifecycle, exact versions, quotas, aggregate claimed-run count and public digests. Raw run IDs, receipts and authority material are never included.', parameters: { deployment_id: { type: 'string' } }, output,
290
+ execute: async (args, exec) => { const scope = this.#scope(exec.agent, 'inspect'); if (args.deployment_id === undefined)
291
+ return { context: JSON.stringify(this.#store.listDeployments(scope).map(publicDeployment)) }; const deployment = this.#store.getDeployment(scope, args.deployment_id); return { context: JSON.stringify(deployment ? publicDeployment(deployment) : null) }; } }));
292
+ });
293
+ ctx.inject(['assistantGoals', 'assistantPolicy', 'assistantDelivery', 'assistantVerifier'], () => { this.#queueReconcile(); });
294
+ // SessionQuery is optional for ordinary manual skills. When it becomes
295
+ // available, retry durable preauthorized captures that previously received
296
+ // the typed `unavailable` bridge result.
297
+ ctx.inject(['sessionQuery'], () => { this.#queueReconcile(); });
298
+ // Evaluation is optional for ordinary saved skills and standalone watches.
299
+ // Qualified deployments bind to it dynamically and fail closed while it is
300
+ // absent; a provider replacement gets a fresh subscription and cold reread.
301
+ ctx.inject(['assistantEvaluation'], runtime => {
302
+ const evaluation = runtime.get('assistantEvaluation');
303
+ if (!evaluation)
304
+ return;
305
+ const unsubscribe = evaluation.onTrustedTaskChange(() => {
306
+ for (const controller of this.#deployedExecutions)
307
+ controller.abort();
308
+ this.#queueReconcile();
309
+ });
310
+ this.#queueReconcile();
311
+ return () => {
312
+ unsubscribe();
313
+ for (const controller of this.#deployedExecutions)
314
+ controller.abort();
315
+ };
316
+ });
317
+ ctx.on('assistant-verifier/receipt', notice => { if (notice.taskKind === 'goal-outcome')
318
+ this.#queueReconcile(); });
319
+ ctx.on('goal/changed', () => this.#queueReconcile());
320
+ ctx.inject(['skills', 'agents'], runtime => {
321
+ const registered = new WeakSet();
322
+ const install = (agent) => {
323
+ if (registered.has(agent))
324
+ return;
325
+ registered.add(agent);
326
+ runtime.effect(() => agent.ctx.get('skills').registerProvider(control => {
327
+ this.#providers.add(control);
328
+ control.signal.addEventListener('abort', () => this.#providers.delete(control), { once: true });
329
+ const summary = (value) => ({ name: value.name, description: value.description,
330
+ invocation: { modelInvocable: true, userInvocable: true }, provider: 'assistant-skills', source: 'runtime' });
331
+ return { name: 'assistant-skills', list: async (options) => {
332
+ if (options.signal?.aborted || control.signal.aborted)
333
+ return [];
334
+ try {
335
+ // Do not cache an owner/Policy observation across turns or revocation.
336
+ return { complete: false, candidates: this.#store.list(this.#scope(agent, 'inspect')).map(value => ({ ...summary(value), rank: 600, locator: { version: value.version } })) };
337
+ }
338
+ catch {
339
+ return [];
340
+ }
341
+ }, get: async (candidate, options) => {
342
+ if (options.signal?.aborted || control.signal.aborted)
343
+ return undefined;
344
+ try {
345
+ const value = this.#store.get(this.#scope(agent, 'inspect'), candidate.name);
346
+ if (!value || value.retired || acceptanceDigest(candidate.locator) !== acceptanceDigest({ version: value.version }))
347
+ return undefined;
348
+ return { ...summary(value), content: this.#body(value) };
349
+ }
350
+ catch {
351
+ return undefined;
352
+ }
353
+ } };
354
+ }), 'assistant-skills.provider');
355
+ };
356
+ runtime.on('agent/created', ({ agent }) => install(agent));
357
+ for (const agent of runtime.agents.list())
358
+ install(agent);
359
+ });
360
+ }
361
+ #body(value) {
362
+ return `# ${value.name}\n${value.description}\n\nSaved tool workflow, version ${value.version}. Source acceptance is historical; it grants no future authority. Run only in a new native Goal with its own independent acceptance conditions. Call skill_run with goal_id, name, version, inputs_json and a stable invocation_id. Do not regenerate the stored tool steps or replay an unknown invocation. Failure stops remaining steps; compensation is explicit owner-directed repair.\n\n${JSON.stringify({ name: value.name, version: value.version, inputs: value.inputs, tools: value.steps.map(step => step.toolName), source: value.source.acceptance })}`;
363
+ }
364
+ #scope(agent, action) {
365
+ if (!this.#active || !agent || this.ctx.get('agents')?.get(agent.id) !== agent)
366
+ throw new Error('assistant-skills: exact live agent required');
367
+ const delivery = this.ctx.get('assistantDelivery', false);
368
+ const owner = delivery?.preferencePrincipalForAgent(agent);
369
+ if (!owner || owner.scope.workspace !== agent.session.header.cwd || owner.scope.preset !== agent.session.header.agentPreset)
370
+ throw new Error('assistant-skills: authenticated owner required');
371
+ const scope = { principalId: owner.principalId, ...owner.principalLineage, workspace: owner.scope.workspace, preset: owner.scope.preset };
372
+ if (['save', 'retire', 'draft', 'activate', 'reject', 'rollback', 'compare', 'watch', 'capture', 'canary'].includes(action)) {
373
+ const current = delivery?.currentPreferenceTurn(agent);
374
+ if (!current || acceptanceDigest({ principalId: current.principalId, ...current.principalLineage, workspace: current.scope.workspace, preset: current.scope.preset }) !== acceptanceDigest(scope))
375
+ throw new Error('assistant-skills: current owner request required');
376
+ }
377
+ const policy = this.ctx.get('assistantPolicy', false);
378
+ if (policy?.evaluateAgent(agent, action, resource).effect !== 'allow')
379
+ throw new Error('assistant-skills: policy denied');
380
+ return scope;
381
+ }
382
+ /** Host continuations deliberately do not consume a current human turn. */
383
+ #repairScope(exec, action, authority) {
384
+ exec.signal.throwIfAborted();
385
+ this.#lifecycle.signal.throwIfAborted();
386
+ if (!this.#active || !exec.agent || this.ctx.get('agents')?.get(exec.agent.id) !== exec.agent
387
+ || !Number.isSafeInteger(authority.expiresAt) || authority.expiresAt <= Date.now()
388
+ || exec.agent.session.header.cwd !== authority.scope.workspace || exec.agent.session.header.agentPreset !== authority.scope.preset)
389
+ throw new Error('assistant-skills: repair authority unavailable');
390
+ const minted = this.#repairAssertions.get(authority.id);
391
+ const record = this.#store.getRepairContinuation(authority.scope, authority.id);
392
+ if (!minted || minted.callback !== authority.assertCurrent || !record || record.authorization.ownerRouteId !== authority.ownerRouteId
393
+ || record.authorization.expiresAt !== authority.expiresAt)
394
+ throw new Error('assistant-skills: unrecognized repair execution capability');
395
+ this.#assertRepair(record);
396
+ authority.assertCurrent();
397
+ this.#watchRoute(authority.scope, authority.ownerRouteId);
398
+ const policy = this.ctx.get('assistantPolicy', false);
399
+ if (!policy || policy.evaluateAgent(exec.agent, action, resource).effect !== 'allow')
400
+ throw new Error('assistant-skills: policy denied');
401
+ return authority.scope;
402
+ }
403
+ #repairAuthorize(exec, action, key, authority) {
404
+ const scope = this.#repairScope(exec, action, authority);
405
+ const policy = this.ctx.get('assistantPolicy', false);
406
+ if (!policy || policy.authorizeAgent(exec.agent, action, resource, { idempotencyKey: `skill-repair-${acceptanceDigest([authority.id, scope, action, key])}` }).effect !== 'allow')
407
+ throw new Error('assistant-skills: policy authorization denied');
408
+ }
409
+ #goals() {
410
+ const goals = this.ctx.get('assistantGoals', false);
411
+ if (!goals)
412
+ throw new Error('assistant-skills: Goals unavailable');
413
+ return goals;
414
+ }
415
+ #authorize(agent, action, key) {
416
+ const policy = this.ctx.get('assistantPolicy', false);
417
+ if (policy?.authorizeAgent(agent, action, resource, { idempotencyKey: `skill-${acceptanceDigest([action, key])}` }).effect !== 'allow')
418
+ throw new Error('assistant-skills: policy authorization denied');
419
+ }
420
+ #changed() { for (const control of this.#providers)
421
+ control.invalidate(); }
422
+ /** Host-only registration; there is intentionally no model tool for sealed inputs or attestations. */
423
+ registerSealedHoldoutProvider = (provider) => {
424
+ if (!this.#active || this.#sealedHoldout !== undefined || !provider || typeof provider.read !== 'function' || typeof provider.generation !== 'string' || !provider.generation)
425
+ throw new Error('assistant-skills: sealed holdout provider unavailable');
426
+ this.#sealedHoldout = provider;
427
+ this.#sealedGeneration = provider.generation;
428
+ return () => { if (this.#sealedHoldout === provider) {
429
+ this.#sealedHoldout = undefined;
430
+ this.#sealedGeneration = undefined;
431
+ } };
432
+ };
433
+ inspectSealedHoldout = (planId, scope) => {
434
+ if (!this.#active || !this.#sealedHoldout)
435
+ throw new Error('assistant-skills: sealed holdout provider unavailable');
436
+ const plan = sealedPlan(this.#sealedHoldout, planId, scope);
437
+ // The opaque plan is deliberately not returned. This is only a Host-attested binding.
438
+ return Object.freeze({ protocol: 'assistant-skills/sealed-holdout-binding/v1', planId, bindingDigest: plan.bindingDigest, attestationDigest: plan.attestationDigest });
439
+ };
440
+ /** Host-only immutable configuration inspection for a previously authorized repair. */
441
+ inspectRepairProfile = (profileId, scope) => {
442
+ if (!this.#active)
443
+ throw new Error('assistant-skills: repair profile unavailable');
444
+ const profile = this.#externalHoldouts.find(value => value.id === profileId && acceptanceDigest(value.scope) === acceptanceDigest(scope));
445
+ if (!profile)
446
+ return undefined;
447
+ return Object.freeze({ profile, digest: acceptanceDigest(profile), templateDigest: profile.canaryAdmissionTemplate === undefined ? undefined : acceptanceDigest(profile.canaryAdmissionTemplate) });
448
+ };
449
+ /** Host-only current definition read; callers must bind its digest in durable intent. */
450
+ inspectRepairParent = (scope, name) => {
451
+ if (!this.#active)
452
+ throw new Error('assistant-skills: repair parent unavailable');
453
+ return this.#store.get(scope, name);
454
+ };
455
+ /** Host entrypoint: consumes opaque provider cases through the same native replay and isolated verifier as compare. */
456
+ qualifySealedHoldout = async (exec, candidateId, planId, invocationId) => {
457
+ const scope = this.#scope(exec.agent, 'compare'), provider = this.#sealedHoldout, generation = this.#sealedGeneration, plan = provider && generation === provider.generation && sealedPlan(provider, planId, scope);
458
+ if (!plan || plan.profile.expiresAt <= Date.now() || acceptanceDigest(plan.profile.scope) !== acceptanceDigest(scope))
459
+ throw new Error('assistant-skills: sealed holdout plan unavailable');
460
+ const candidate = this.#store.getCandidate(scope, candidateId);
461
+ if (!candidate?.parentDigest)
462
+ throw new Error('assistant-skills: sealed qualification requires an existing parent');
463
+ const claim = this.#store.claimComparison(scope, { sessionId: String(exec.agent.session.id), candidateId, parentDigest: candidate.parentDigest, profileId: `sealed-${planId}`, profileDigest: plan.bindingDigest, invocationId }, plan.profile.maxComparisons);
464
+ const current = (running = true) => {
465
+ exec.signal.throwIfAborted();
466
+ this.#lifecycle.signal.throwIfAborted();
467
+ const fresh = this.#sealedHoldout === provider && this.#sealedGeneration === generation && provider?.generation === generation ? sealedPlan(provider, planId, scope) : undefined;
468
+ if (!fresh || fresh.bindingDigest !== plan.bindingDigest || Date.now() >= plan.profile.expiresAt || acceptanceDigest(this.#scope(exec.agent, 'compare')) !== acceptanceDigest(scope) || running && this.#store.getComparison(scope, claim.comparison.id)?.state !== 'running')
469
+ throw new Error('assistant-skills: sealed qualification authority changed');
470
+ this.#pending(scope, candidateId);
471
+ };
472
+ if (!claim.claimed) {
473
+ current(false);
474
+ return claim.comparison;
475
+ }
476
+ const operation = (async () => {
477
+ try {
478
+ this.#authorize(exec.agent, 'compare', claim.comparison.id);
479
+ current();
480
+ const baseline = this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
481
+ const comparator = new SkillComparator(plan.profile);
482
+ try {
483
+ const result = await comparator.compare(claim.comparison.id, baseline, candidate.definition, AbortSignal.any([exec.signal, this.#lifecycle.signal]), current);
484
+ current();
485
+ return this.#store.finishComparison(scope, claim.comparison.id, result.report.complete && result.report.variants.every(value => value.unknown === 0) ? 'complete' : 'unknown', { ...result, hostAttestedHoldout: { bindingDigest: plan.bindingDigest }, promotionAuthorized: false });
486
+ }
487
+ finally {
488
+ await comparator.close();
489
+ }
490
+ }
491
+ catch {
492
+ this.#store.finishComparison(scope, claim.comparison.id, 'unknown', { reason: 'sealed-qualification-failed-or-authority-changed', promotionAuthorized: false });
493
+ throw new Error(`assistant-skills: sealed qualification ${claim.comparison.id} is unknown; inspect status, do not replay`);
494
+ }
495
+ })();
496
+ this.#comparing.add(operation);
497
+ try {
498
+ return await operation;
499
+ }
500
+ finally {
501
+ this.#comparing.delete(operation);
502
+ }
503
+ };
504
+ save(agent, goalId, options, expectedVersion = 0) {
505
+ const scope = this.#scope(agent, 'save');
506
+ const source = this.#goals().inspectVerifiedWorkflowSource(agent, goalId);
507
+ if (acceptanceDigest(source.scope) !== acceptanceDigest(scope))
508
+ throw new Error('assistant-skills: source owner mismatch');
509
+ const definition = this.#capturedDefinition(source, options, scope);
510
+ this.#authorize(agent, 'save', [scope, definition, expectedVersion]);
511
+ const saved = this.#store.save(scope, definition, expectedVersion);
512
+ this.#changed();
513
+ return saved;
514
+ }
515
+ #capturedDefinition(source, options, scope) {
516
+ return createDefinition(source, options, this.#allowed, captureRunExpansions(source, scope, this.#store));
517
+ }
518
+ inspect(agent, runId) {
519
+ const scope = this.#scope(agent, 'inspect');
520
+ return runId ? this.#store.getRun(scope, runId) : this.#store.list(scope);
521
+ }
522
+ retire(agent, name, expectedVersion) {
523
+ const scope = this.#scope(agent, 'retire');
524
+ this.#authorize(agent, 'retire', [scope, name, expectedVersion]);
525
+ const result = this.#store.retire(scope, name, expectedVersion);
526
+ this.#changed();
527
+ return result;
528
+ }
529
+ comparisonStatus(agent, id) {
530
+ const scope = this.#scope(agent, 'inspect');
531
+ if (id)
532
+ return this.#comparisonStatus(scope, id);
533
+ return [
534
+ ...this.#comparisons.filter(profile => acceptanceDigest(profile.scope) === acceptanceDigest(scope)).map(profile => ({ id: profile.id, version: profile.version, kind: 'local', executionTool: 'skill_compare', expiresAt: profile.expiresAt, cases: profile.cases.length, repeats: profile.repeats, maxComparisons: profile.maxComparisons })),
535
+ ...this.#externalHoldouts.filter(profile => acceptanceDigest(profile.scope) === acceptanceDigest(scope)).map(profile => ({ id: profile.id, version: profile.version, kind: 'external', executionTool: 'skill_qualify', ...(profile.authority.generatorDigest === undefined || profile.canaryAdmission === undefined ? {} : { canaryExecutionTool: 'skill_canary' }), expiresAt: profile.execution.expiresAt, maxComparisons: profile.maxComparisons })),
536
+ ];
537
+ }
538
+ #comparisonStatus(scope, id) {
539
+ const comparison = this.#store.getComparison(scope, id);
540
+ if (!comparison)
541
+ return null;
542
+ const result = plainArguments(comparison.result);
543
+ const receipt = result && plainArguments(result.receipt);
544
+ const quality = result && plainArguments(result.quality);
545
+ return { id: comparison.id, candidateId: comparison.candidateId, parentDigest: comparison.parentDigest, profileId: comparison.profileId, profileDigest: comparison.profileDigest, invocationId: comparison.invocationId,
546
+ state: comparison.state, createdAt: comparison.createdAt, updatedAt: comparison.updatedAt,
547
+ ...(quality === undefined ? {} : { quality: { candidateChecksPassed: quality.candidateChecksPassed, evaluationGain: quality.evaluationGain, evaluationGainObserved: quality.evaluationGainObserved, criticalRegressionsPassed: quality.criticalRegressionsPassed, heldoutIndependence: quality.heldoutIndependence } }),
548
+ ...(receipt === undefined ? {} : { planDigest: receipt.planDigest, datasetDigest: receipt.datasetDigest }),
549
+ ...(result?.admissionDigest === undefined ? {} : { admissionDigest: result.admissionDigest }),
550
+ ...(receipt && plainArguments(receipt.prospective)?.generatorDigest !== undefined ? { generatorDigest: plainArguments(receipt.prospective).generatorDigest } : {}) };
551
+ }
552
+ /** Fixed external authority qualification. Private cells, authority keys and operator configuration never become tool arguments or status data. */
553
+ async qualifyExternalHoldout(exec, candidateId, profileId, invocationId, action = 'compare', currentAuthority, repair) {
554
+ const scope = repair ? this.#repairScope(exec, action, repair.authority) : this.#scope(exec.agent, action);
555
+ const profile = repair?.profile ?? this.#externalHoldouts.find(value => value.id === profileId && acceptanceDigest(value.scope) === acceptanceDigest(scope));
556
+ if (!profile || profile.execution.expiresAt <= Date.now())
557
+ throw new Error('assistant-skills: current external holdout profile required');
558
+ const profileDigest = acceptanceDigest(profile);
559
+ const candidate = this.#store.getCandidate(scope, candidateId);
560
+ if (!candidate?.parentDigest)
561
+ throw new Error('assistant-skills: external qualification requires an existing parent');
562
+ const baseline = this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
563
+ if (!baseline || acceptanceDigest(baseline) !== candidate.parentDigest)
564
+ throw new Error('assistant-skills: candidate parent changed');
565
+ if (profile.canaryAdmission !== undefined && !canaryAdmissionMatches(profile.canaryAdmission, baseline, candidate.definition))
566
+ throw new Error('assistant-skills: external holdout admission does not match exact definitions');
567
+ const candidateDigest = acceptanceDigest(candidate);
568
+ const claim = this.#store.claimComparison(scope, { sessionId: String(exec.agent.session.id), candidateId, parentDigest: candidate.parentDigest, profileId: `external:${profile.id}:${profile.version}`, profileDigest, invocationId }, 1);
569
+ const current = (running = true) => {
570
+ exec.signal.throwIfAborted();
571
+ this.#lifecycle.signal.throwIfAborted();
572
+ currentAuthority?.();
573
+ const fresh = this.#externalHoldouts.find(value => value.id === profile.id && value.version === profile.version && acceptanceDigest(value.scope) === acceptanceDigest(scope));
574
+ if (!fresh || acceptanceDigest(fresh) !== (repair?.configuredDigest ?? profileDigest) || Date.now() >= profile.execution.expiresAt
575
+ || acceptanceDigest(repair ? this.#repairScope(exec, action, repair.authority) : this.#scope(exec.agent, action)) !== acceptanceDigest(scope)
576
+ || acceptanceDigest(this.#store.getCandidate(scope, candidateId)) !== candidateDigest
577
+ || running && this.#store.getComparison(scope, claim.comparison.id)?.state !== 'running')
578
+ throw new Error('assistant-skills: external qualification authority changed');
579
+ this.#pending(scope, candidateId);
580
+ };
581
+ if (!claim.claimed) {
582
+ current(false);
583
+ return this.#comparisonStatus(scope, claim.comparison.id);
584
+ }
585
+ const operation = (async () => {
586
+ try {
587
+ if (repair)
588
+ this.#repairAuthorize(exec, 'compare', claim.comparison.id, repair.authority);
589
+ else
590
+ this.#authorize(exec.agent, 'compare', claim.comparison.id);
591
+ current();
592
+ const currentBaseline = this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
593
+ if (!currentBaseline || acceptanceDigest(currentBaseline) !== candidate.parentDigest || profile.canaryAdmission !== undefined && !canaryAdmissionMatches(profile.canaryAdmission, currentBaseline, candidate.definition))
594
+ throw new Error('assistant-skills: candidate parent or holdout admission changed');
595
+ const opened = await openHoldoutProcess(profile.authority, AbortSignal.any([exec.signal, this.#lifecycle.signal]));
596
+ let result;
597
+ try {
598
+ result = await qualifyHoldout({ baseline: currentBaseline, candidate: candidate.definition, scope,
599
+ execution: { ...profile.execution, stateRoot: join(profile.execution.stateRoot, claim.comparison.id) }, ...(profile.inputs === undefined ? {} : { inputs: profile.inputs }), ...(profile.files === undefined ? {} : { files: profile.files }),
600
+ ...(profile.canaryAdmission === undefined ? {} : { canaryAdmission: profile.canaryAdmission }),
601
+ pinnedPublicKey: profile.authority.publicKey, ...(profile.authority.datasetDigest === undefined ? {} : { expectedDatasetDigest: profile.authority.datasetDigest }), ...(profile.authority.generatorDigest === undefined ? {} : { expectedGeneratorDigest: profile.authority.generatorDigest }), transport: opened.transport,
602
+ signal: AbortSignal.any([exec.signal, this.#lifecycle.signal]), authorize: current });
603
+ }
604
+ finally {
605
+ await opened.close();
606
+ }
607
+ current();
608
+ return this.#store.finishComparison(scope, claim.comparison.id, result.receipt.complete && result.receipt.cellVerdicts.every(value => value.verdict !== 'unknown') ? 'complete' : 'unknown', result);
609
+ }
610
+ catch {
611
+ this.#store.finishComparison(scope, claim.comparison.id, 'unknown', { reason: 'external-qualification-failed-or-authority-changed', promotionAuthorized: false });
612
+ throw new Error(`assistant-skills: external qualification ${claim.comparison.id} is unknown; inspect status, do not replay`);
613
+ }
614
+ })();
615
+ this.#comparing.add(operation);
616
+ try {
617
+ await operation;
618
+ return this.#comparisonStatus(scope, claim.comparison.id);
619
+ }
620
+ finally {
621
+ this.#comparing.delete(operation);
622
+ }
623
+ }
624
+ #promotePolicy(scope) {
625
+ return { subject: { kind: 'background', id: 'dsh-enhanced-assistant-skills', workspace: scope.workspace, principal: scope.principalId },
626
+ action: 'promote', resource, context: { initiator: 'background' } };
627
+ }
628
+ #deploymentCurrent(deployment) {
629
+ const scope = deployment.scope;
630
+ if (!this.#active || deployment.expiresAt <= Date.now() || !['canary', 'promoted', 'blocked'].includes(deployment.state))
631
+ throw new Error('assistant-skills: deployment authority ended');
632
+ const watch = this.#store.listWatches(scope).find(value => value.id === deployment.watchId);
633
+ if (!watch || watch.ownerRouteId.length === 0 || acceptanceDigest(watch.routeReceipt) !== acceptanceDigest(deployment.routeReceipt))
634
+ throw new Error('assistant-skills: deployment route changed');
635
+ this.#watchRoute(scope, watch.ownerRouteId, deployment.routeReceipt);
636
+ this.#watchAuthorized(watch);
637
+ const current = this.#store.get(scope, deployment.skillName);
638
+ if (!current || current.retired || current.version !== deployment.version || acceptanceDigest(current) !== deployment.definitionDigest)
639
+ throw new Error('assistant-skills: deployment superseded');
640
+ const comparison = this.#store.getComparison(scope, deployment.comparisonId);
641
+ if (!comparison || comparison.state !== 'complete' || acceptanceDigest(comparison.result) !== deployment.qualificationDigest)
642
+ throw new Error('assistant-skills: deployment qualification changed');
643
+ const profile = this.#externalHoldouts.find(value => `external:${value.id}:${value.version}` === comparison.profileId && acceptanceDigest(value.scope) === acceptanceDigest(scope));
644
+ const candidate = this.#store.getCandidate(scope, deployment.candidateId);
645
+ const parent = candidate && this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
646
+ const admission = profile?.canaryAdmission ?? (profile?.canaryAdmissionTemplate !== undefined && parent && candidate ? materializeCanaryAdmission(profile.canaryAdmissionTemplate, parent, candidate.definition) : undefined);
647
+ const effectiveProfile = profile && admission ? { ...profile, canaryAdmission: admission } : undefined;
648
+ if (!profile || !admission || !effectiveProfile || acceptanceDigest(effectiveProfile) !== comparison.profileDigest || profile.execution.expiresAt < deployment.expiresAt
649
+ || !candidate || !parent || !canaryAdmissionMatches(admission, parent, candidate.definition)
650
+ || acceptanceDigest(admission) !== deployment.admissionDigest
651
+ || admission.candidateDefinitionDigest !== deployment.candidateDefinitionDigest
652
+ || acceptanceDigest(admission.taskFamily) !== acceptanceDigest(deployment.taskFamily)
653
+ || acceptanceDigest(watch.taskFamily ?? null) !== acceptanceDigest(deployment.taskFamily)
654
+ || comparison.result.admissionDigest !== deployment.admissionDigest)
655
+ throw new Error('assistant-skills: deployment profile changed');
656
+ }
657
+ #deploymentAuthorized(deployment, authorize = false) {
658
+ if (deployment.state !== 'canary' && deployment.state !== 'promoted')
659
+ throw new Error('assistant-skills: deployment authority ended');
660
+ this.#deploymentCurrent(deployment);
661
+ const policy = this.ctx.get('assistantPolicy', false);
662
+ if (!policy || policy.evaluate(this.#promotePolicy(deployment.scope)).effect !== 'allow')
663
+ throw new Error('assistant-skills: deployment promotion authority ended');
664
+ if (authorize && policy.authorize(this.#promotePolicy(deployment.scope), { idempotencyKey: `${deployment.id}:promote` }).effect !== 'allow')
665
+ throw new Error('assistant-skills: deployment promotion authorization denied');
666
+ }
667
+ #deploymentCanonicalEvidenceCurrent(deployment) {
668
+ const evaluation = this.ctx.get('assistantEvaluation');
669
+ if (!evaluation || typeof evaluation.getTrustedGoalOutcomeLearningProjection !== 'function'
670
+ || typeof evaluation.withTrustedCanonicalTaskWriterFence !== 'function')
671
+ throw new Error('assistant-skills: canonical deployment evidence unavailable');
672
+ const scope = deployment.scope;
673
+ const watch = this.#store.listWatches(scope).find(value => value.id === deployment.watchId);
674
+ if (!watch || watch.observations.some(value => value.canonical === undefined))
675
+ throw new Error('assistant-skills: canonical deployment evidence unavailable');
676
+ const evaluationScope = evaluation.canonicalHostScope({ workspace: scope.workspace, preset: scope.preset });
677
+ let scopeWatermark;
678
+ const evidence = [];
679
+ for (const expected of watch.canonicalRevisions ?? []) {
680
+ const run = this.#store.getRun(scope, expected.runId);
681
+ if (!run)
682
+ throw new Error('assistant-skills: canonical deployment evidence changed');
683
+ const current = evaluation.getTrustedGoalOutcomeLearningProjection({ scope: evaluationScope, assessmentId: expected.subjectRef });
684
+ if (!current || current.projection.subjectKind !== expected.subjectKind || current.projection.subjectRef !== expected.subjectRef
685
+ || current.projection.version !== expected.version || current.projection.digest !== expected.digest
686
+ || current.projection.disposition !== expected.disposition)
687
+ throw new Error('assistant-skills: canonical deployment evidence changed');
688
+ const first = this.#goals().inspectOwnerGoalExecution({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: run.sessionId, goalId: run.goalId });
689
+ if (!watch.taskFamily || !watchBindingCurrent(first, scope, run, watch.taskFamily, expected.binding, current))
690
+ throw new Error('assistant-skills: canonical deployment evidence changed');
691
+ const second = this.#goals().inspectOwnerGoalExecution({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: run.sessionId, goalId: run.goalId });
692
+ if (acceptanceDigest(second) !== acceptanceDigest(first))
693
+ throw new Error('assistant-skills: canonical deployment evidence changed');
694
+ if (scopeWatermark !== undefined && current.scopeWatermark !== scopeWatermark)
695
+ throw new Error('assistant-skills: canonical deployment evidence changed');
696
+ scopeWatermark = current.scopeWatermark;
697
+ evidence.push({
698
+ subjectKind: expected.subjectKind, subjectRef: expected.subjectRef, version: expected.version, digest: expected.digest, disposition: expected.disposition,
699
+ });
700
+ }
701
+ if (scopeWatermark === undefined)
702
+ return;
703
+ const fenced = evaluation.withTrustedCanonicalTaskWriterFence({ scope: evaluationScope, scopeWatermark, evidence }, () => true);
704
+ if (!fenced.matched)
705
+ throw new Error('assistant-skills: canonical deployment evidence changed');
706
+ }
707
+ #watchEvidenceCurrent(evaluation, scope, watch, run, result, canonical) {
708
+ if (!watch.taskFamily)
709
+ return;
710
+ const evaluationScope = evaluation.canonicalHostScope({ workspace: scope.workspace, preset: scope.preset });
711
+ const observations = watch.observations.filter(value => value.runId !== run.id);
712
+ if (result.kind === 'current')
713
+ observations.push(result.observation);
714
+ const revisions = (watch.canonicalRevisions ?? []).filter(value => value.runId !== run.id);
715
+ const resultBinding = result.binding ?? watch.canonicalRevisions?.find(value => value.runId === run.id)?.binding;
716
+ if (!resultBinding)
717
+ return;
718
+ const resultRevision = result.kind === 'current' ? result.observation.canonical : result.canonical;
719
+ revisions.push({ runId: run.id, ...resultRevision, binding: resultBinding });
720
+ const evidence = [];
721
+ for (const revision of revisions) {
722
+ const observation = observations.find(value => value.runId === revision.runId);
723
+ const observedRun = this.#store.getRun(scope, revision.runId);
724
+ if (!observedRun || revision.disposition === 'upsert' && (!observation || observation.taskFamilyDigest !== acceptanceDigest(watch.taskFamily)))
725
+ return;
726
+ const current = revision.runId === run.id ? canonical : evaluation.getTrustedGoalOutcomeLearningProjection({ scope: evaluationScope, assessmentId: revision.subjectRef });
727
+ if (!current || current.scopeWatermark !== canonical.scopeWatermark || current.projection.subjectKind !== revision.subjectKind
728
+ || current.projection.subjectRef !== revision.subjectRef || current.projection.version !== revision.version
729
+ || current.projection.digest !== revision.digest || current.projection.disposition !== revision.disposition
730
+ || revision.disposition === 'upsert' && current.objective?.status !== observation.objectiveStatus)
731
+ return;
732
+ const first = this.#goals().inspectOwnerGoalExecution({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: observedRun.sessionId, goalId: observedRun.goalId });
733
+ if (!watchBindingCurrent(first, scope, observedRun, watch.taskFamily, revision.binding, current))
734
+ return;
735
+ const second = this.#goals().inspectOwnerGoalExecution({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: observedRun.sessionId, goalId: observedRun.goalId });
736
+ if (acceptanceDigest(second) !== acceptanceDigest(first))
737
+ return;
738
+ evidence.push({ subjectKind: revision.subjectKind, subjectRef: revision.subjectRef, version: revision.version, digest: revision.digest, disposition: revision.disposition });
739
+ }
740
+ return { scope: evaluationScope, scopeWatermark: canonical.scopeWatermark, evidence };
741
+ }
742
+ #canaryCurrent(exec, scope, profile, input, receipt, authority) {
743
+ if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now() || input.expiresAt > profile.execution.expiresAt || input.expiresAt > Date.now() + 7 * 86400000
744
+ || !Number.isSafeInteger(input.maxRuns) || input.maxRuns < 1 || input.maxRuns > 100 || !Number.isSafeInteger(input.canaryRuns) || input.canaryRuns < 1 || input.canaryRuns > input.maxRuns)
745
+ throw new Error('assistant-skills: invalid canary authorization');
746
+ const current = (action) => authority ? this.#repairScope(exec, action, authority) : this.#scope(exec.agent, action);
747
+ if (acceptanceDigest(current('canary')) !== acceptanceDigest(scope) || acceptanceDigest(current('compare')) !== acceptanceDigest(scope) || acceptanceDigest(current('watch')) !== acceptanceDigest(scope))
748
+ throw new Error('assistant-skills: canary authority changed');
749
+ const route = this.#watchRoute(scope, input.ownerRouteId, receipt);
750
+ const policy = this.ctx.get('assistantPolicy', false);
751
+ if (!policy || policy.evaluate(this.#promotePolicy(scope)).effect !== 'allow'
752
+ || ['watch', 'rollback'].some(action => policy.evaluate(this.#watchPolicy(scope, action)).effect !== 'allow'))
753
+ throw new Error('assistant-skills: configure finite promotion and rollback authority');
754
+ return route;
755
+ }
756
+ async canary(exec, candidateId, profileId, invocationId, input, repair) {
757
+ input = Object.freeze({ ...input });
758
+ const scope = repair ? this.#repairScope(exec, 'canary', repair) : this.#scope(exec.agent, 'canary');
759
+ if (repair && input.ownerRouteId !== repair.ownerRouteId)
760
+ throw new Error('assistant-skills: repair authority route changed');
761
+ const candidate = this.#store.getCandidate(scope, candidateId);
762
+ if (!candidate)
763
+ throw new Error('assistant-skills: candidate unavailable');
764
+ const configured = this.#externalHoldouts.find(value => value.id === profileId && acceptanceDigest(value.scope) === acceptanceDigest(scope));
765
+ const profile = configured;
766
+ if (!profile)
767
+ throw new Error('assistant-skills: prospective external holdout profile required');
768
+ const route = this.#canaryCurrent(exec, scope, profile, input, undefined, repair);
769
+ if (!profile.authority.generatorDigest || (!repair && profile.canaryAdmission === undefined) || repair && profile.canaryAdmissionTemplate === undefined)
770
+ throw new Error('assistant-skills: bound prospective external holdout profile required; call skill_comparison_status without comparison_id and use the exact id whose canaryExecutionTool is skill_canary');
771
+ const baselineBeforeQualification = this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
772
+ if (!baselineBeforeQualification)
773
+ throw new Error('assistant-skills: prospective holdout admission does not match exact definitions');
774
+ const admission = repair ? materializeCanaryAdmission(profile.canaryAdmissionTemplate, baselineBeforeQualification, candidate.definition) : profile.canaryAdmission;
775
+ const effectiveProfile = repair ? Object.freeze({ ...profile, canaryAdmission: admission }) : profile;
776
+ if (!canaryAdmissionMatches(admission, baselineBeforeQualification, candidate.definition))
777
+ throw new Error('assistant-skills: prospective holdout admission does not match exact definitions');
778
+ // A candidate can only ever receive one deployment. A retry must match the
779
+ // original finite authorization exactly; it cannot renew or mutate it.
780
+ if (candidate.state === 'activated') {
781
+ const existing = candidate.deploymentId ? this.#store.getDeployment(scope, candidate.deploymentId) : undefined;
782
+ const watch = existing && this.#store.listWatches(scope).find(value => value.id === existing.watchId);
783
+ if (!existing || !watch || existing.comparisonId === '' || existing.expiresAt !== input.expiresAt || existing.maxRuns !== input.maxRuns || existing.canaryRuns !== input.canaryRuns || watch.ownerRouteId !== input.ownerRouteId)
784
+ throw new Error('assistant-skills: canary authorization conflict');
785
+ const comparison = this.#store.getComparison(scope, existing.comparisonId);
786
+ if (!comparison || comparison.profileId !== `external:${profile.id}:${profile.version}` || comparison.invocationId !== invocationId)
787
+ throw new Error('assistant-skills: canary authorization conflict');
788
+ this.#watchRoute(scope, input.ownerRouteId, existing.routeReceipt);
789
+ const definition = this.#store.get(scope, existing.skillName, existing.version);
790
+ if (!definition)
791
+ throw new Error('assistant-skills: canary deployment definition unavailable');
792
+ return { definition: publicDefinition(definition), deployment: publicDeployment(existing), replayed: true };
793
+ }
794
+ this.#pending(scope, candidateId);
795
+ if (repair)
796
+ this.#repairAuthorize(exec, 'canary', [scope, candidateId, profileId, invocationId, input, route], repair);
797
+ else
798
+ this.#authorize(exec.agent, 'canary', [scope, candidateId, profileId, invocationId, input, route]);
799
+ const qualified = await this.qualifyExternalHoldout(exec, candidateId, profileId, invocationId, 'canary', () => { this.#canaryCurrent(exec, scope, effectiveProfile, input, route, repair); }, repair ? { authority: repair, profile: effectiveProfile, configuredDigest: acceptanceDigest(profile) } : undefined);
800
+ const comparison = this.#store.getComparison(scope, qualified.id);
801
+ const baseline = this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
802
+ if (!comparison || !baseline || candidate.state !== 'pending')
803
+ throw new Error('assistant-skills: canary qualification unavailable');
804
+ const prospective = inspectProspectiveQualification(comparison.result, { scope, baseline, candidate: candidate.definition, execution: profile.execution,
805
+ ...(profile.inputs === undefined ? {} : { inputs: profile.inputs }), ...(profile.files === undefined ? {} : { files: profile.files }), pinnedPublicKey: profile.authority.publicKey, expectedGeneratorDigest: profile.authority.generatorDigest, canaryAdmission: admission });
806
+ if (!prospective || prospective.prospectiveHoldout !== 'authority-attested-after-freeze' || prospective.quality.candidateChecksPassed !== true || prospective.quality.evaluationGainObserved !== true || prospective.quality.criticalRegressionsPassed !== true)
807
+ throw new Error('assistant-skills: prospective qualification gates failed');
808
+ this.#canaryCurrent(exec, scope, effectiveProfile, input, route, repair);
809
+ if (repair)
810
+ this.#repairAuthorize(exec, 'watch', [scope, candidateId, comparison.id, input, route], repair);
811
+ else
812
+ this.#authorize(exec.agent, 'watch', [scope, candidateId, comparison.id, input, route]);
813
+ this.#canaryCurrent(exec, scope, effectiveProfile, input, route, repair);
814
+ const activated = this.#store.activateQualifiedCandidate(scope, candidateId, comparison.id, acceptanceDigest(comparison.result), admission, input, route);
815
+ this.#changed();
816
+ this.#queueReconcile();
817
+ return { definition: publicDefinition(activated.definition), deployment: publicDeployment(activated.deployment), replayed: false };
818
+ }
819
+ /** Host-only repair continuation. It requires a dynamic template, never a model-visible admission. */
820
+ canaryOwnerAuthorizedRepair = (exec, candidateId, profileId, invocationId, deploymentInput, authority) => {
821
+ return this.canary(exec, candidateId, profileId, invocationId, deploymentInput, authority);
822
+ };
823
+ async compare(exec, candidateId, profileId, invocationId) {
824
+ const scope = this.#scope(exec.agent, 'compare');
825
+ const profile = this.#comparisons.find(value => value.id === profileId && acceptanceDigest(value.scope) === acceptanceDigest(scope));
826
+ if (!profile || profile.expiresAt <= Date.now())
827
+ throw new Error('assistant-skills: current comparison profile required');
828
+ const candidate = this.#store.getCandidate(scope, candidateId);
829
+ if (!candidate?.parentDigest)
830
+ throw new Error('assistant-skills: comparison requires an existing parent');
831
+ const claim = this.#store.claimComparison(scope, { sessionId: String(exec.agent.session.id), candidateId, parentDigest: candidate.parentDigest, profileId, profileDigest: acceptanceDigest(profile), invocationId }, profile.maxComparisons);
832
+ if (!claim.claimed)
833
+ return this.#comparisonStatus(scope, claim.comparison.id);
834
+ const operation = (async () => {
835
+ try {
836
+ this.#authorize(exec.agent, 'compare', claim.comparison.id);
837
+ const current = () => {
838
+ exec.signal.throwIfAborted();
839
+ this.#lifecycle.signal.throwIfAborted();
840
+ if (Date.now() >= profile.expiresAt || acceptanceDigest(this.#scope(exec.agent, 'compare')) !== acceptanceDigest(scope)
841
+ || this.#store.getComparison(scope, claim.comparison.id)?.state !== 'running')
842
+ throw new Error('assistant-skills: comparison authority changed');
843
+ this.#pending(scope, candidateId);
844
+ };
845
+ current();
846
+ const baseline = this.#store.get(scope, candidate.definition.name, candidate.parentVersion);
847
+ let pending = this.#comparators.get(profile.id);
848
+ if (!pending) {
849
+ pending = import('./comparison.js').then(({ SkillComparator }) => new SkillComparator(profile));
850
+ this.#comparators.set(profile.id, pending);
851
+ }
852
+ const result = await (await pending).compare(claim.comparison.id, baseline, candidate.definition, AbortSignal.any([exec.signal, this.#lifecycle.signal]), current);
853
+ current();
854
+ return this.#store.finishComparison(scope, claim.comparison.id, result.report.complete && result.report.variants.every(value => value.unknown === 0) ? 'complete' : 'unknown', result);
855
+ }
856
+ catch {
857
+ this.#store.finishComparison(scope, claim.comparison.id, 'unknown', { reason: 'comparison-failed-or-authority-changed', promotionAuthorized: false });
858
+ throw new Error(`assistant-skills: comparison ${claim.comparison.id} is unknown; inspect status, do not replay`);
859
+ }
860
+ })();
861
+ this.#comparing.add(operation);
862
+ try {
863
+ await operation;
864
+ return this.#comparisonStatus(scope, claim.comparison.id);
865
+ }
866
+ finally {
867
+ this.#comparing.delete(operation);
868
+ }
869
+ }
870
+ stage(agent, goalId, options, parentVersion, reason, trigger) {
871
+ const scope = this.#scope(agent, 'draft');
872
+ const source = this.#goals().inspectVerifiedWorkflowSource(agent, goalId);
873
+ if (acceptanceDigest(source.scope) !== acceptanceDigest(scope))
874
+ throw new Error('assistant-skills: source owner mismatch');
875
+ const definition = this.#capturedDefinition(source, options, scope);
876
+ this.#authorize(agent, 'draft', [scope, definition, parentVersion, reason, trigger]);
877
+ return this.#preview(scope, this.#store.stageCandidate(scope, definition, { expectedVersion: parentVersion, reason, trigger, expiresAt: Date.now() + this.#candidateTtl }));
878
+ }
879
+ async stageFailureCandidate(exec, input, repair) {
880
+ const scope = repair ? this.#repairScope(exec, 'draft', repair) : this.#scope(exec.agent, 'draft');
881
+ if (repair && (acceptanceDigest(repair.scope) !== acceptanceDigest(scope) || repair.ownerRouteId !== input.ownerRouteId))
882
+ throw new Error('assistant-skills: repair authority scope changed');
883
+ const route = this.#watchRoute(scope, input.ownerRouteId);
884
+ const window = failureWindow(input);
885
+ const failureGoals = () => {
886
+ const value = this.#goals();
887
+ if (typeof value.trustedAcceptanceProducerGeneration !== 'function' || typeof value.inspectOwnerFailureCaptureSummary !== 'function'
888
+ || typeof value.inspectOwnerVerifiedWorkflowSource !== 'function')
889
+ throw new Error('assistant-skills: upgrade assistant-goals to use skill_failure_candidate');
890
+ return value;
891
+ };
892
+ const goals = failureGoals();
893
+ const generation = goals.trustedAcceptanceProducerGeneration();
894
+ if (typeof generation !== 'string' || generation.length < 1 || generation.length > 256)
895
+ throw new Error('assistant-skills: invalid Goals producer generation');
896
+ const evidenceInput = { ownerRouteId: input.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, taskFamilyId: input.taskFamilyId,
897
+ repair: { sessionId: input.repairSessionId, goalId: input.repairGoalId }, failures: window.failures, minimumOccurrences: window.minimumOccurrences };
898
+ const repairInput = { ownerRouteId: input.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: input.repairSessionId, goalId: input.repairGoalId };
899
+ const signal = AbortSignal.any([exec.signal, this.#lifecycle.signal]);
900
+ // The Goals capability performs its own atomic double-read before issuing
901
+ // this attestation. Read it exactly once: attestedAt is intentionally fresh
902
+ // and therefore makes two otherwise equivalent summary digests differ.
903
+ const [failure, firstRepair] = await Promise.all([goals.inspectOwnerFailureCaptureSummary(evidenceInput, signal), goals.inspectOwnerVerifiedWorkflowSource(repairInput, signal)]);
904
+ const firstRepairDigest = acceptanceDigest(firstRepair);
905
+ if (failure.evidence.generation !== generation)
906
+ throw new Error('assistant-skills: Goals failure evidence generation changed');
907
+ const parent = this.#store.get(scope, input.name, input.parentVersion), activeParent = this.#store.get(scope, input.name);
908
+ if (!parent || parent.retired || !activeParent || activeParent.version !== input.parentVersion || acceptanceDigest(activeParent) !== acceptanceDigest(parent))
909
+ throw new Error('assistant-skills: exact current parent required');
910
+ const definition = this.#capturedDefinition(firstRepair, { name: input.name, description: input.description, ...(input.bindings === undefined ? {} : { bindings: input.bindings }) }, scope);
911
+ const currentGoals = failureGoals();
912
+ const currentRepair = await currentGoals.inspectOwnerVerifiedWorkflowSource(repairInput, signal);
913
+ if (currentGoals.trustedAcceptanceProducerGeneration() !== generation || failure.evidence.generation !== generation
914
+ || acceptanceDigest(currentRepair) !== firstRepairDigest
915
+ || acceptanceDigest(repair ? this.#repairScope(exec, 'draft', repair) : this.#scope(exec.agent, 'draft')) !== acceptanceDigest(scope))
916
+ throw new Error('assistant-skills: Goals failure evidence changed during capture');
917
+ this.#watchRoute(scope, input.ownerRouteId, route);
918
+ const provenance = captureFailureCandidateProvenance(failure, currentRepair, scope, parent, definition);
919
+ const provenanceDigest = acceptanceDigest(provenance);
920
+ if (repair)
921
+ this.#repairAuthorize(exec, 'draft', [scope, definition, input.parentVersion, provenanceDigest], repair);
922
+ else
923
+ this.#authorize(exec.agent, 'draft', [scope, definition, input.parentVersion, provenanceDigest]);
924
+ if (failureGoals().trustedAcceptanceProducerGeneration() !== generation
925
+ || acceptanceDigest(repair ? this.#repairScope(exec, 'draft', repair) : this.#scope(exec.agent, 'draft')) !== acceptanceDigest(scope))
926
+ throw new Error('assistant-skills: Goals producer authority changed');
927
+ this.#watchRoute(scope, input.ownerRouteId, route);
928
+ const currentParent = this.#store.get(scope, input.name);
929
+ if (!currentParent || currentParent.version !== input.parentVersion || acceptanceDigest(currentParent) !== acceptanceDigest(parent))
930
+ throw new Error('assistant-skills: candidate parent changed');
931
+ const single = window.minimumOccurrences === 1 && window.failures.length === 1;
932
+ const reason = single ? 'Host-verified, evidence-bound repair after an independently verified failure.'
933
+ : 'Host-verified, evidence-bound repair after independently verified repeated failures.';
934
+ const trigger = single ? `failure:${input.taskFamilyId}:${window.failures[0].goalId}`
935
+ : `failure:${input.taskFamilyId}:${failure.failureCategory}:${failure.failures.length}`;
936
+ return this.#preview(scope, this.#store.stageCandidate(scope, definition, { expectedVersion: input.parentVersion, reason, trigger, expiresAt: Date.now() + this.#candidateTtl, failureProvenance: provenance }));
937
+ }
938
+ stageOwnerAuthorizedFailureCandidate = (exec, input, authority) => {
939
+ return this.stageFailureCandidate(exec, input, authority);
940
+ };
941
+ #preview(scope, candidate) {
942
+ const parent = candidate.parentVersion ? this.#store.get(scope, candidate.definition.name, candidate.parentVersion) : undefined;
943
+ return publicCandidate(candidate, parent);
944
+ }
945
+ candidates(agent, id) {
946
+ const scope = this.#scope(agent, 'inspect');
947
+ if (!id)
948
+ return this.#store.listCandidates(scope).map(candidate => this.#preview(scope, candidate));
949
+ const candidate = this.#store.getCandidate(scope, id);
950
+ return candidate ? this.#preview(scope, candidate) : null;
951
+ }
952
+ #pending(scope, id) {
953
+ const candidate = this.#store.getCandidate(scope, id);
954
+ if (!candidate || candidate.state !== 'pending' || candidate.expiresAt <= Date.now())
955
+ throw new Error('assistant-skills: candidate unavailable');
956
+ const current = this.#store.get(scope, candidate.definition.name);
957
+ if ((current?.version ?? 0) !== candidate.parentVersion || (current ? acceptanceDigest(current) : null) !== candidate.parentDigest)
958
+ throw new Error('assistant-skills: candidate parent changed');
959
+ return candidate;
960
+ }
961
+ async trial(exec, candidateId, goalId, inputs, invocationId) {
962
+ const scope = this.#scope(exec.agent, 'trial'), candidate = this.#pending(scope, candidateId);
963
+ return this.#run(exec, goalId, { ...candidate.definition, version: candidate.parentVersion + 1, parentVersion: candidate.parentVersion || null, retired: false, createdAt: candidate.createdAt, updatedAt: candidate.updatedAt }, inputs, invocationId, candidateId);
964
+ }
965
+ activate(agent, candidateId, trialRunId, observation) {
966
+ const scope = this.#scope(agent, 'activate');
967
+ const candidate = this.#store.getCandidate(scope, candidateId), run = this.#store.getRun(scope, trialRunId);
968
+ if (!candidate || !run || run.candidateId !== candidateId || run.state !== 'succeeded' || !run.goalExecutionRunId || run.sessionId !== String(agent.session.id))
969
+ throw new Error('assistant-skills: successful exact trial required');
970
+ const watch = observation && { input: { ...observation, skillName: candidate.definition.name, version: candidate.parentVersion + 1, fallbackVersion: candidate.parentVersion }, routeReceipt: this.#watchRoute(scope, observation.ownerRouteId) };
971
+ const watchAuthority = () => {
972
+ if (!watch)
973
+ return;
974
+ const policy = this.ctx.get('assistantPolicy', false);
975
+ if (candidate.parentVersion < 1 || acceptanceDigest(this.#scope(agent, 'watch')) !== acceptanceDigest(scope) || !policy
976
+ || policy.evaluate(this.#watchPolicy(scope, 'watch')).effect !== 'allow')
977
+ throw new Error('assistant-skills: current activation watch authority required');
978
+ this.#watchRoute(scope, watch.input.ownerRouteId, watch.routeReceipt);
979
+ };
980
+ const result = (activated, activeVersion) => {
981
+ const activeWatch = watch && this.#store.listWatches(scope).find(value => value.id === this.#store.getCandidate(scope, candidateId)?.activationWatchId);
982
+ return { activated: publicDefinition(activated), activeVersion, replayed: false, improvement: 'unmeasured', ...(activeWatch === undefined ? {} : { watch: publicWatch(activeWatch) }) };
983
+ };
984
+ watchAuthority();
985
+ // A lost activation response can be recovered without renewing proof or changing a later version.
986
+ if (candidate.state === 'activated' && candidate.trialRunId === trialRunId && candidate.acceptanceDigest) {
987
+ return result(this.#store.activateCandidate(scope, candidateId, trialRunId, candidate.acceptanceDigest, watch), this.#store.get(scope, candidate.definition.name)?.version ?? null);
988
+ }
989
+ this.#pending(scope, candidateId);
990
+ const verify = () => {
991
+ const proof = this.#goals().inspectVerifiedWorkflowRun(agent, run.goalId, run.goalExecutionRunId);
992
+ if (acceptanceDigest(proof.scope) !== acceptanceDigest(scope) || proof.goal.sessionId !== run.sessionId || proof.runId !== run.goalExecutionRunId
993
+ || !trialProofSteps(proof.steps, candidateId, trialRunId, run.goalId, run.invocationId, run.inputs))
994
+ throw new Error('assistant-skills: independent exact trial acceptance required');
995
+ return acceptanceDigest(proof.acceptance);
996
+ };
997
+ const receipt = verify();
998
+ this.#authorize(agent, 'activate', [scope, candidateId, trialRunId]);
999
+ if (watch)
1000
+ this.#authorize(agent, 'watch', [scope, candidateId, trialRunId, watch.input]);
1001
+ if (acceptanceDigest(this.#scope(agent, 'activate')) !== acceptanceDigest(scope) || verify() !== receipt)
1002
+ throw new Error('assistant-skills: trial authority changed');
1003
+ watchAuthority();
1004
+ const activated = this.#store.activateCandidate(scope, candidateId, trialRunId, receipt, watch);
1005
+ this.#changed();
1006
+ return result(activated, activated.version);
1007
+ }
1008
+ reject(agent, candidateId) {
1009
+ const scope = this.#scope(agent, 'reject');
1010
+ this.#authorize(agent, 'reject', [scope, candidateId]);
1011
+ return this.#preview(scope, this.#store.rejectCandidate(scope, candidateId));
1012
+ }
1013
+ rollback(agent, name, expectedVersion, targetVersion) {
1014
+ const scope = this.#scope(agent, 'rollback');
1015
+ this.#authorize(agent, 'rollback', [scope, name, expectedVersion, targetVersion]);
1016
+ const restored = this.#store.rollback(scope, name, expectedVersion, targetVersion);
1017
+ this.#changed();
1018
+ return restored;
1019
+ }
1020
+ /** Process-local capability check used by Goals; no tool can mint these assertions. */
1021
+ ownsOwnerAuthorizedRepair = (input, callback) => {
1022
+ const minted = this.#repairAssertions.get(input.authorizationId);
1023
+ if (!minted || minted.callback !== callback || minted.triggerDigest !== acceptanceDigest(input.trigger))
1024
+ return false;
1025
+ const record = this.#store.getRepairContinuation(input.scope, input.authorizationId);
1026
+ if (!record || record.authorizationDigest !== input.authorizationDigest || record.authorization.ownerRouteId !== input.ownerRouteId
1027
+ || record.authorization.expiresAt !== input.expiresAt || input.objective !== input.trigger.taskFamily.objective)
1028
+ return false;
1029
+ try {
1030
+ this.#assertRepair(record);
1031
+ return this.#repairProfile(record).maxGoalRounds === input.maxGoalRounds;
1032
+ }
1033
+ catch {
1034
+ return false;
1035
+ }
1036
+ };
1037
+ #repairProfileAt(record, iteration) {
1038
+ const entry = record.authorization.profileSequence?.[iteration - 1]
1039
+ ?? (iteration === 1 ? { id: record.authorization.profileId, digest: record.authorization.profileDigest } : undefined);
1040
+ const profile = entry && this.#repairProfiles.find(value => value.id === entry.id && acceptanceDigest(value.scope) === acceptanceDigest(record.scope));
1041
+ const holdout = profile && this.inspectRepairProfile(profile.externalHoldoutProfileId, profile.scope);
1042
+ if (!entry || !profile || !holdout || acceptanceDigest([profile, holdout.digest]) !== entry.digest)
1043
+ throw new Error('assistant-skills: repair profile changed');
1044
+ return profile;
1045
+ }
1046
+ #repairProfile(record) { return this.#repairProfileAt(record, record.iteration); }
1047
+ #repairSource(record) {
1048
+ if (record.iteration === 1)
1049
+ return record.authorization.source;
1050
+ const source = record.checkpoint.source;
1051
+ if (!source || record.checkpoint.profileId !== this.#repairProfile(record).id)
1052
+ throw new Error('assistant-skills: repair iteration source unavailable');
1053
+ return source;
1054
+ }
1055
+ #repairParent(record) {
1056
+ if (record.iteration === 1)
1057
+ return { version: record.authorization.parentVersion, digest: record.authorization.parentDigest };
1058
+ const id = record.checkpoint.predecessorDeploymentId;
1059
+ const deployment = typeof id === 'string' ? this.#store.getDeployment(record.scope, id) : undefined;
1060
+ if (!deployment || deployment.promotedAt === undefined || deployment.ownerRouteId !== record.authorization.ownerRouteId
1061
+ || deployment.skillName !== record.authorization.skillName || acceptanceDigest(deployment.routeReceipt) !== acceptanceDigest(record.routeReceipt))
1062
+ throw new Error('assistant-skills: repair predecessor is not a current promoted deployment');
1063
+ if (deployment.state !== 'promoted') {
1064
+ const candidateId = record.checkpoint.candidateId;
1065
+ const candidate = typeof candidateId === 'string' ? this.#store.getCandidate(record.scope, candidateId) : undefined;
1066
+ const successor = candidate?.deploymentId ? this.#store.getDeployment(record.scope, candidate.deploymentId) : undefined;
1067
+ const profile = this.#repairProfile(record);
1068
+ if (deployment.state !== 'superseded' || !['comparing', 'watching', 'complete'].includes(record.state)
1069
+ || !candidate || candidate.state !== 'activated' || candidate.parentVersion !== deployment.version || candidate.parentDigest !== deployment.definitionDigest
1070
+ || !successor || successor.candidateId !== candidate.id || successor.parentVersion !== deployment.version
1071
+ || successor.version !== candidate.activatedVersion || successor.skillName !== deployment.skillName
1072
+ || successor.ownerRouteId !== record.authorization.ownerRouteId || successor.expiresAt !== record.authorization.expiresAt
1073
+ || successor.maxRuns !== profile.maxCanaryRuns || successor.canaryRuns !== profile.canaryRuns
1074
+ || acceptanceDigest(successor.routeReceipt) !== acceptanceDigest(record.routeReceipt)
1075
+ || record.state !== 'comparing' && record.checkpoint.deploymentId !== successor.id)
1076
+ throw new Error('assistant-skills: repair predecessor superseded outside its authorized successor');
1077
+ }
1078
+ return { version: deployment.version, digest: deployment.definitionDigest };
1079
+ }
1080
+ #repairLocator(record, repair = false) {
1081
+ const scope = record.scope;
1082
+ const locator = repair ? record.checkpoint.repair : this.#repairSource(record);
1083
+ if (!locator?.sessionId || !locator.goalId)
1084
+ throw new Error('assistant-skills: repair locator unavailable');
1085
+ return { ownerRouteId: record.authorization.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: locator.sessionId, goalId: locator.goalId };
1086
+ }
1087
+ #assertRepair(record) {
1088
+ const current = this.#store.getRepairContinuation(record.scope, record.id);
1089
+ if (!this.#active || !current || current.authorizationDigest !== record.authorizationDigest || current.authorization.expiresAt <= Date.now()
1090
+ || ['complete', 'rejected', 'revoked', 'expired', 'unknown'].includes(current.state))
1091
+ throw new Error('assistant-skills: repair authorization ended');
1092
+ const scope = record.scope, profile = this.#repairProfile(current);
1093
+ this.#watchRoute(scope, current.authorization.ownerRouteId, current.routeReceipt);
1094
+ const policy = this.ctx.get('assistantPolicy', false);
1095
+ if (!policy || ['draft', 'compare', 'canary', 'promote', 'watch', 'rollback'].some(action => policy.evaluate({
1096
+ subject: { kind: 'background', id: 'dsh-enhanced-assistant-skills', workspace: scope.workspace, principal: scope.principalId },
1097
+ action, resource, context: { initiator: 'background' },
1098
+ }).effect !== 'allow'))
1099
+ throw new Error('assistant-skills: repair background policy denied');
1100
+ for (let iteration = 1; iteration <= current.authorization.maxIterations; iteration++)
1101
+ this.#repairProfileAt(current, iteration);
1102
+ const expectedSource = this.#repairSource(current), expectedParent = this.#repairParent(current);
1103
+ const source = this.#goals().inspectOwnerGoalExecution(this.#repairLocator(current)).storedGoal;
1104
+ if (source.definition.digest !== expectedSource.definitionDigest || source.nativeAtLastObservation.goalId !== expectedSource.nativeGoalId)
1105
+ throw new Error('assistant-skills: repair source changed');
1106
+ const parent = this.#store.get(scope, profile.skillName);
1107
+ if (parent && !parent.retired && parent.version === expectedParent.version && acceptanceDigest(parent) === expectedParent.digest)
1108
+ return;
1109
+ // Activation by this exact comparison may advance the parent during its final await.
1110
+ const candidateId = current.checkpoint.candidateId;
1111
+ const candidate = typeof candidateId === 'string' ? this.#store.getCandidate(scope, candidateId) : undefined;
1112
+ const deployment = candidate?.deploymentId ? this.#store.getDeployment(scope, candidate.deploymentId) : undefined;
1113
+ if (!['comparing', 'watching'].includes(current.state) || !parent || !candidate || !deployment || deployment.candidateId !== candidate.id
1114
+ || deployment.parentVersion !== expectedParent.version || deployment.ownerRouteId !== current.authorization.ownerRouteId
1115
+ || deployment.expiresAt !== current.authorization.expiresAt || deployment.maxRuns !== profile.maxCanaryRuns || deployment.canaryRuns !== profile.canaryRuns
1116
+ || acceptanceDigest(deployment.routeReceipt) !== acceptanceDigest(current.routeReceipt)
1117
+ || parent.version !== deployment.version || acceptanceDigest(parent) !== deployment.definitionDigest)
1118
+ throw new Error('assistant-skills: authorized repair parent changed');
1119
+ }
1120
+ #repairAuthority(record) {
1121
+ const minted = this.#repairAssertions.get(record.id);
1122
+ if (!minted)
1123
+ throw new Error('assistant-skills: repair execution capability unavailable');
1124
+ return { id: record.id, scope: record.scope, ownerRouteId: record.authorization.ownerRouteId, expiresAt: record.authorization.expiresAt, assertCurrent: minted.callback };
1125
+ }
1126
+ #repairExec(record, signal) {
1127
+ const agent = this.#repairAgents?.get(this.#repairLocator(record, true).sessionId);
1128
+ if (!agent)
1129
+ throw new Error('assistant-skills: repair Agent unavailable; uncertain work must not be recreated');
1130
+ return { agent, signal };
1131
+ }
1132
+ #publicRepair(record) {
1133
+ return { id: record.id, state: record.state, iteration: record.iteration, maxIterations: record.authorization.maxIterations,
1134
+ expiresAt: record.authorization.expiresAt, profileId: record.authorization.profileId, skillName: record.authorization.skillName,
1135
+ sourceGoalId: this.#repairSource(record).goalId, ...(record.checkpoint.repair ? { repair: record.checkpoint.repair } : {}),
1136
+ ...(record.checkpoint.candidateId ? { candidateId: record.checkpoint.candidateId } : {}),
1137
+ ...(record.checkpoint.deploymentId ? { deploymentId: record.checkpoint.deploymentId } : {}),
1138
+ ...(record.checkpoint.failure ? { failure: record.checkpoint.failure } : {}), updatedAt: record.updatedAt };
1139
+ }
1140
+ armRepair = (agent, input) => {
1141
+ const scope = this.#scope(agent, 'draft'), route = this.#watchRoute(scope, input.ownerRouteId);
1142
+ const profile = this.#repairProfiles.find(value => value.id === input.profileId && acceptanceDigest(value.scope) === acceptanceDigest(scope));
1143
+ const holdout = profile && this.inspectRepairProfile(profile.externalHoldoutProfileId, scope);
1144
+ if (!this.#repairRuntime || !profile || !holdout?.profile.canaryAdmissionTemplate || !Number.isSafeInteger(input.expiresAt)
1145
+ || input.expiresAt <= Date.now() || input.expiresAt > Math.min(holdout.profile.execution.expiresAt, Date.now() + 604800000))
1146
+ throw new Error('assistant-skills: finite current repair profile required');
1147
+ const parent = this.#store.get(scope, profile.skillName);
1148
+ const sessionId = input.sessionId ?? String(agent.session.id);
1149
+ const snapshot = this.#goals().inspectOwnerGoalExecution({ ownerRouteId: input.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId, goalId: input.goalId });
1150
+ if (!parent || parent.retired || snapshot.storedGoal.definition.digest !== holdout.profile.canaryAdmissionTemplate.taskFamily.goalDefinitionDigest)
1151
+ throw new Error('assistant-skills: exact repair source family and current parent required');
1152
+ const sequenceIds = [profile.id, ...(profile.followupProfileIds ?? [])];
1153
+ if (sequenceIds.length !== (profile.maxIterations ?? 1))
1154
+ throw new Error('assistant-skills: every finite repair iteration needs an explicit profile');
1155
+ const profileSequence = sequenceIds.map(id => {
1156
+ const next = this.#repairProfiles.find(value => value.id === id && acceptanceDigest(value.scope) === acceptanceDigest(scope));
1157
+ const nextHoldout = next && this.inspectRepairProfile(next.externalHoldoutProfileId, scope);
1158
+ if (!next || !nextHoldout || next.skillName !== profile.skillName || next.provider !== profile.provider || next.model !== profile.model
1159
+ || next.allowedTools.some(tool => !profile.allowedTools.includes(tool)) || next.maxOutputTokens > profile.maxOutputTokens || next.maxGoalRounds > profile.maxGoalRounds || next.maxDurationMs > profile.maxDurationMs
1160
+ || input.expiresAt > nextHoldout.profile.execution.expiresAt)
1161
+ throw new Error('assistant-skills: followup profile exceeds the frozen repair authority');
1162
+ return { id, digest: acceptanceDigest([next, nextHoldout.digest]) };
1163
+ });
1164
+ let feedbackAuthority;
1165
+ if (input.notify === true) {
1166
+ const delivery = this.ctx.get('assistantDelivery', false);
1167
+ const resolved = delivery?.resolveOwnerRoute(input.ownerRouteId);
1168
+ if (!resolved || resolved.binding.sessionId !== String(agent.session.id))
1169
+ throw new Error('assistant-skills: repair feedback requires the current owner route Session');
1170
+ const permission = this.ctx.get('assistantPolicy', false)?.evaluate({ subject: { kind: 'background', id: 'dsh-enhanced-assistant-skills', workspace: scope.workspace, principal: scope.principalId },
1171
+ action: 'send', resource: { kind: 'message', id: resolved.binding.id }, context: { initiator: 'background' } });
1172
+ if (permission?.effect !== 'allow')
1173
+ throw new Error('assistant-skills: repair feedback policy denied');
1174
+ feedbackAuthority = { sessionId: resolved.binding.sessionId, expiresAt: input.expiresAt, routeReceipt: route };
1175
+ }
1176
+ const authorization = { invocationId: input.invocationId, ownerRouteId: input.ownerRouteId,
1177
+ source: { goalId: input.goalId, sessionId, nativeGoalId: snapshot.storedGoal.nativeAtLastObservation.goalId, definitionDigest: snapshot.storedGoal.definition.digest },
1178
+ profileId: profile.id, profileDigest: acceptanceDigest([profile, holdout.digest]), skillName: profile.skillName,
1179
+ parentVersion: parent.version, parentDigest: acceptanceDigest(parent), maxIterations: profile.maxIterations ?? 1, expiresAt: input.expiresAt,
1180
+ ...(profileSequence.length > 1 ? { profileSequence } : {}), ...(feedbackAuthority ? { feedbackAuthority } : {}) };
1181
+ for (const action of ['draft', 'compare', 'canary', 'watch']) {
1182
+ this.#scope(agent, action);
1183
+ this.#authorize(agent, action, ['repair-arm', scope, authorization, route]);
1184
+ }
1185
+ this.#watchRoute(scope, input.ownerRouteId, route);
1186
+ const record = this.#store.createRepairContinuation(scope, authorization, route);
1187
+ this.#queueRepair();
1188
+ return this.#publicRepair(record);
1189
+ };
1190
+ repairStatus = (agent, id) => {
1191
+ const scope = this.#scope(agent, 'inspect');
1192
+ if (id !== undefined) {
1193
+ const record = this.#store.getRepairContinuation(scope, id);
1194
+ return record ? this.#publicRepair(record) : null;
1195
+ }
1196
+ return { profiles: this.#repairProfiles.filter(value => acceptanceDigest(value.scope) === acceptanceDigest(scope)).map(value => ({ id: value.id, skillName: value.skillName, taskFamilyId: value.taskFamilyId, maxIterations: value.maxIterations ?? 1, maxGoalRounds: value.maxGoalRounds, maxModelCalls: value.maxModelCalls, maxToolCalls: value.maxToolCalls, maxDurationMs: value.maxDurationMs })),
1197
+ continuations: this.#store.listRepairContinuations(scope).map(value => this.#publicRepair(value)) };
1198
+ };
1199
+ revokeRepair = async (agent, id) => {
1200
+ const scope = this.#scope(agent, 'reject'), record = this.#store.getRepairContinuation(scope, id);
1201
+ if (!record)
1202
+ throw new Error('assistant-skills: repair continuation unavailable');
1203
+ this.#authorize(agent, 'reject', ['repair-revoke', id]);
1204
+ if (['complete', 'rejected', 'revoked', 'expired', 'unknown'].includes(record.state))
1205
+ return this.#publicRepair(record);
1206
+ const revoked = this.#store.transitionRepairContinuation(scope, id, record.revision, 'revoked', record.checkpoint);
1207
+ await this.#repairAgents?.closeAuthorization(record.id);
1208
+ this.#repairAssertions.delete(record.id);
1209
+ return this.#publicRepair(revoked);
1210
+ };
1211
+ #publishRepairFeedback(record) {
1212
+ const feedbackAuthority = record.authorization.feedbackAuthority;
1213
+ const delivery = this.ctx.get('assistantDelivery', false);
1214
+ if (!feedbackAuthority || !delivery || ['revoked', 'expired'].includes(record.state))
1215
+ return;
1216
+ const publish = (iteration, milestone) => enqueueRepairFeedback(delivery, { ...record, scope: record.scope, feedbackAuthority }, { iteration, maxIterations: record.authorization.maxIterations, state: record.state, milestone });
1217
+ const history = Array.isArray(record.checkpoint.iterationHistory) ? record.checkpoint.iterationHistory : [];
1218
+ for (let index = 0; index < history.length; index++) {
1219
+ const id = history[index].predecessorDeploymentId;
1220
+ if (id && this.#store.getDeployment(record.scope, id)?.promotedAt !== undefined)
1221
+ publish(index + 1, 'iteration-success');
1222
+ }
1223
+ if (record.state === 'watching' || record.state === 'complete') {
1224
+ const id = record.checkpoint.deploymentId;
1225
+ const deployment = typeof id === 'string' ? this.#store.getDeployment(record.scope, id) : undefined;
1226
+ if (deployment?.state === 'canary')
1227
+ publish(record.iteration, 'finite-canary');
1228
+ if (deployment?.state === 'promoted')
1229
+ publish(record.iteration, 'iteration-success');
1230
+ }
1231
+ if (record.state === 'complete')
1232
+ publish(record.iteration, 'final-success');
1233
+ if (record.state === 'rejected')
1234
+ publish(record.iteration, 'successor-failed');
1235
+ if (record.state === 'unknown')
1236
+ publish(record.iteration, 'interrupted');
1237
+ }
1238
+ #assertRepairSuccessor(record, input) {
1239
+ this.#assertRepair(record);
1240
+ const profile = this.#repairProfileAt(record, record.iteration + 1);
1241
+ const holdout = this.inspectRepairProfile(profile.externalHoldoutProfileId, record.scope);
1242
+ const deployment = this.#store.getDeployment(record.scope, input.predecessorDeploymentId);
1243
+ const trigger = input.trigger;
1244
+ const sourceRun = deployment?.promotedAt === undefined ? undefined : this.#store.repairSourceRuns(record.scope, profile.skillName, deployment.version, deployment.promotedAt)
1245
+ .find(run => run.goalId === input.source.goalId && run.sessionId === input.source.sessionId && run.nativeGoalId === input.source.nativeGoalId && run.goalDefinitionDigest === input.source.definitionDigest);
1246
+ if (input.profileId !== profile.id || input.predecessorDeploymentId !== record.checkpoint.deploymentId || deployment?.state !== 'promoted' || deployment.promotedAt === undefined
1247
+ || sourceRun === undefined
1248
+ || input.source.definitionDigest !== holdout?.profile.canaryAdmissionTemplate?.taskFamily.goalDefinitionDigest
1249
+ || trigger.taskFamily.definitionDigest !== input.source.definitionDigest || trigger.taskFamily.id !== profile.taskFamilyId
1250
+ || acceptanceDigest(trigger.scope) !== acceptanceDigest(record.scope)
1251
+ || !trigger.failures.some(failure => failure.goal.id === input.source.goalId && failure.goal.sessionId === input.source.sessionId
1252
+ && failure.goal.nativeGoalId === input.source.nativeGoalId))
1253
+ throw new Error('assistant-skills: successor repair is not the authorized new failed task');
1254
+ }
1255
+ #queueRepair() {
1256
+ const runtime = this.#repairRuntime;
1257
+ if (!this.#active || !runtime || this.#repairTask)
1258
+ return;
1259
+ this.#repairTask = runtime.tickAll().catch(() => []).then(async () => {
1260
+ for (const record of this.#store.listRepairContinuations()) {
1261
+ this.#publishRepairFeedback(record);
1262
+ if (!['watching', 'complete', 'rejected', 'revoked', 'expired', 'unknown'].includes(record.state))
1263
+ continue;
1264
+ const repair = record.checkpoint.repair;
1265
+ if (repair)
1266
+ await this.#repairAgents?.closeSession(repair.sessionId);
1267
+ this.#repairAssertions.delete(record.id);
1268
+ }
1269
+ }).catch(() => undefined).finally(() => { this.#repairTask = undefined; });
1270
+ }
1271
+ #installRepairRuntime(ctx) {
1272
+ ctx.inject(['tools', 'agents', 'sessions', 'llm', 'systemPrompt', 'assistantGoals', 'assistantPolicy', 'assistantDelivery', 'assistantVerifier'], runtime => {
1273
+ const agents = new OwnerRepairAgentRuntime(runtime);
1274
+ const driver = new RepairContinuationRuntime(this.#store, {
1275
+ assertCurrent: record => this.#assertRepair(record),
1276
+ inspectTrigger: async (record, signal) => {
1277
+ const snapshot = this.#goals().inspectOwnerGoalExecution(this.#repairLocator(record));
1278
+ if (snapshot.outcome?.status !== 'not-achieved' || snapshot.storedGoal.nativeAtLastObservation.phase === 'active')
1279
+ return undefined;
1280
+ const profile = this.#repairProfile(record), locator = this.#repairLocator(record);
1281
+ return this.#goals().inspectOwnerFailureTrigger({ ownerRouteId: locator.ownerRouteId, principalId: locator.principalId, workspace: locator.workspace, preset: locator.preset, taskFamilyId: profile.taskFamilyId, failures: [{ goalId: locator.goalId, sessionId: locator.sessionId }], minimumOccurrences: 1 }, signal);
1282
+ },
1283
+ createRepair: async (record, evidence, signal) => {
1284
+ const profile = this.#repairProfile(record), primary = this.#repairProfileAt(record, 1), trigger = evidence;
1285
+ const usage = this.#store.repairUsage(record.scope, record.id);
1286
+ const callback = () => this.#assertRepair(record);
1287
+ this.#repairAssertions.set(record.id, { callback, triggerDigest: acceptanceDigest(trigger) });
1288
+ return agents.create({ ...profile, id: record.id, iteration: record.iteration, maxModelCalls: primary.maxModelCalls, maxToolCalls: primary.maxToolCalls,
1289
+ initialModelCalls: usage.modelCalls, initialToolCalls: usage.toolCalls,
1290
+ recordUsage: kind => { this.#store.chargeRepairUsage(record.scope, record.id, kind, kind === 'model' ? primary.maxModelCalls : primary.maxToolCalls); }, authorizationDigest: record.authorizationDigest, ownerRouteId: record.authorization.ownerRouteId,
1291
+ trigger, objective: trigger.taskFamily.objective, expiresAt: record.authorization.expiresAt, assertCurrent: callback }, signal);
1292
+ },
1293
+ inspectRepair: async (record, signal) => {
1294
+ const locator = this.#repairLocator(record, true), snapshot = this.#goals().inspectOwnerGoalExecution(locator);
1295
+ if (Date.now() >= record.updatedAt + this.#repairProfile(record).maxDurationMs)
1296
+ return 'rejected';
1297
+ if (snapshot.outcome?.status === 'achieved' && snapshot.storedGoal.nativeAtLastObservation.phase === 'complete') {
1298
+ await this.#goals().inspectOwnerVerifiedWorkflowSource(locator, signal);
1299
+ return 'achieved';
1300
+ }
1301
+ const phase = snapshot.storedGoal.nativeAtLastObservation.phase;
1302
+ // Native round exhaustion precedes asynchronous independent outcome settlement.
1303
+ if (phase === 'blocked' && ['pending', 'unverified', 'achieved'].includes(snapshot.outcome?.status ?? 'unverified'))
1304
+ return 'running';
1305
+ return ['blocked', 'cleared', 'paused', 'complete'].includes(phase) ? 'rejected' : 'running';
1306
+ },
1307
+ capture: async (record, signal) => {
1308
+ const profile = this.#repairProfile(record), locator = this.#repairLocator(record, true), source = this.#repairSource(record);
1309
+ const candidate = await this.stageOwnerAuthorizedFailureCandidate(this.#repairExec(record, signal), {
1310
+ ownerRouteId: record.authorization.ownerRouteId, triggerGoalId: source.goalId, triggerSessionId: source.sessionId,
1311
+ repairGoalId: locator.goalId, repairSessionId: locator.sessionId, taskFamilyId: profile.taskFamilyId,
1312
+ name: profile.skillName, description: profile.description, ...(profile.bindings === undefined ? {} : { bindings: profile.bindings }), parentVersion: this.#repairParent(record).version,
1313
+ }, this.#repairAuthority(record));
1314
+ return { candidateId: candidate.id };
1315
+ },
1316
+ compare: async (record, signal) => {
1317
+ const profile = this.#repairProfile(record), candidateId = record.checkpoint.candidateId;
1318
+ if (typeof candidateId !== 'string')
1319
+ throw new Error('assistant-skills: repair candidate unavailable');
1320
+ const result = await this.canaryOwnerAuthorizedRepair(this.#repairExec(record, signal), candidateId, profile.externalHoldoutProfileId, `repair-${record.id}-${record.iteration}`, { ownerRouteId: record.authorization.ownerRouteId, expiresAt: record.authorization.expiresAt,
1321
+ maxRuns: profile.maxCanaryRuns, canaryRuns: profile.canaryRuns }, this.#repairAuthority(record));
1322
+ return { deploymentId: result.deployment.id };
1323
+ },
1324
+ assertNextIteration: (record, next) => this.#assertRepairSuccessor(record, next),
1325
+ inspectNextIteration: async (record, signal) => {
1326
+ const id = record.checkpoint.deploymentId;
1327
+ const deployment = typeof id === 'string' ? this.#store.getDeployment(record.scope, id) : undefined;
1328
+ if (!deployment || deployment.state !== 'promoted' || deployment.promotedAt === undefined)
1329
+ return undefined;
1330
+ const profile = this.#repairProfileAt(record, record.iteration + 1);
1331
+ const holdout = this.inspectRepairProfile(profile.externalHoldoutProfileId, record.scope);
1332
+ const candidates = this.#store.repairSourceRuns(record.scope, profile.skillName, deployment.version, deployment.promotedAt);
1333
+ for (const run of candidates) {
1334
+ if (run.goalDefinitionDigest !== holdout?.profile.canaryAdmissionTemplate?.taskFamily.goalDefinitionDigest)
1335
+ continue;
1336
+ const locator = { ...this.#repairLocator(record), goalId: run.goalId, sessionId: run.sessionId };
1337
+ const snapshot = this.#goals().inspectOwnerGoalExecution(locator);
1338
+ if (snapshot.outcome?.status !== 'not-achieved' || snapshot.storedGoal.nativeAtLastObservation.phase === 'active')
1339
+ continue;
1340
+ const trigger = await this.#goals().inspectOwnerFailureTrigger({ ownerRouteId: locator.ownerRouteId, principalId: locator.principalId,
1341
+ workspace: locator.workspace, preset: locator.preset, taskFamilyId: profile.taskFamilyId,
1342
+ failures: [{ goalId: run.goalId, sessionId: run.sessionId }], minimumOccurrences: 1 }, signal);
1343
+ const next = { profileId: profile.id, source: { goalId: run.goalId, sessionId: run.sessionId,
1344
+ nativeGoalId: snapshot.storedGoal.nativeAtLastObservation.goalId, definitionDigest: snapshot.storedGoal.definition.digest }, trigger, predecessorDeploymentId: deployment.id };
1345
+ this.#assertRepairSuccessor(record, next);
1346
+ await agents.closeSession(this.#repairLocator(record, true).sessionId);
1347
+ this.#repairAssertions.delete(record.id);
1348
+ this.#assertRepairSuccessor(record, next);
1349
+ return next;
1350
+ }
1351
+ return undefined;
1352
+ },
1353
+ inspectDeployment: async (record) => {
1354
+ const id = record.checkpoint.deploymentId;
1355
+ const deployment = typeof id === 'string' ? this.#store.getDeployment(record.scope, id) : undefined;
1356
+ if (!deployment)
1357
+ throw new Error('assistant-skills: repair deployment unavailable');
1358
+ return deployment.state === 'promoted' ? 'complete' : deployment.state === 'canary' ? 'watching' : 'rejected';
1359
+ },
1360
+ });
1361
+ this.#repairAgents = agents;
1362
+ this.#repairRuntime = driver;
1363
+ for (const record of this.#store.listRepairContinuations()) {
1364
+ // An in-flight native repair cannot be recreated from a cached transcript.
1365
+ // Until its Agent can be reacquired with verified quiescence, surface the
1366
+ // interruption immediately rather than leaving it "running" until expiry.
1367
+ if (['repairing', 'repair-achieved', 'candidate-staged'].includes(record.state)) {
1368
+ this.#store.transitionRepairContinuation(record.scope, record.id, record.revision, 'unknown', { ...record.checkpoint, failure: 'repair-agent-interrupted' });
1369
+ }
1370
+ else
1371
+ driver.recover(record.scope, record.id);
1372
+ }
1373
+ runtime.tools.register(defineTool({ name: 'skill_repair_arm', description: 'Authorize a bounded autonomous repair sequence starting from an exact owner Goal using configured profiles. After independent failure evidence, a separate native Goal synthesizes a repair; only independent acceptance and prospective comparison can deploy a finite canary. Requires the current human request. Followups require the frozen profile sequence and a new independently failed task after promotion; budgets and expiry cannot be renewed.',
1374
+ parameters: { goal_id: { type: 'string', required: true, description: businessGoalId }, source_session_id: { type: 'string' }, profile_id: { type: 'string', required: true }, owner_route_id: { type: 'string', required: true }, invocation_id: { type: 'string', required: true }, expires_at: { type: 'integer', required: true }, notify: { type: 'boolean', description: 'Send finite repair results to this exact current owner route Session.' } }, output,
1375
+ execute: async (args, exec) => ({ context: JSON.stringify(this.armRepair(exec.agent, { goalId: args.goal_id, ...(args.source_session_id === undefined ? {} : { sessionId: args.source_session_id }), profileId: args.profile_id, ownerRouteId: args.owner_route_id, invocationId: args.invocation_id, expiresAt: args.expires_at, ...(args.notify === undefined ? {} : { notify: args.notify }) })) }) }));
1376
+ runtime.tools.register(defineTool({ name: 'skill_repair_status', description: 'Discover owner-scoped repair profile ids and finite limits, or inspect repair, candidate and deployment progress. Unknown work must not be replayed.', parameters: { repair_id: { type: 'string' } }, output,
1377
+ execute: async (args, exec) => ({ context: JSON.stringify(this.repairStatus(exec.agent, args.repair_id)) }) }));
1378
+ runtime.tools.register(defineTool({ name: 'skill_repair_revoke', description: 'Revoke this owner’s remaining repair continuation and stop its active repair Agent. Already completed effects and any deployed version retain their explicit rollback controls.', parameters: { repair_id: { type: 'string', required: true } }, output,
1379
+ execute: async (args, exec) => ({ context: JSON.stringify(await this.revokeRepair(exec.agent, args.repair_id)) }) }));
1380
+ let stopped;
1381
+ const timer = setInterval(() => this.#queueRepair(), 1000);
1382
+ const stop = () => stopped ??= (async () => {
1383
+ clearInterval(timer);
1384
+ if (this.#repairRuntime === driver) {
1385
+ this.#repairRuntime = undefined;
1386
+ this.#repairAgents = undefined;
1387
+ }
1388
+ // Close model admission first, then settle state transitions, then release handles.
1389
+ const task = this.#repairTask;
1390
+ await Promise.all([driver.dispose(), agents.dispose()]);
1391
+ await task;
1392
+ this.#repairAssertions.clear();
1393
+ })();
1394
+ this.#repairStops.add(stop);
1395
+ runtime.effect(() => stop, 'assistant-skills.repair-runtime');
1396
+ this.#queueRepair();
1397
+ });
1398
+ }
1399
+ #watchPolicy(scope, action) {
1400
+ return { subject: { kind: 'background', id: 'dsh-enhanced-assistant-skills', workspace: scope.workspace, principal: scope.principalId },
1401
+ action, resource, context: { initiator: 'background' } };
1402
+ }
1403
+ #watchRoute(scope, ownerRouteId, expected) {
1404
+ const route = this.ctx.get('assistantDelivery', false)?.validateOwnerRoute({ authorityId: ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, agentPreset: scope.preset });
1405
+ if (!route || route.principalRecordId !== scope.principalRecordId || route.principalVersion !== scope.principalVersion
1406
+ || expected !== undefined && acceptanceDigest(route) !== acceptanceDigest(expected))
1407
+ throw new Error('assistant-skills: owner route changed');
1408
+ return route;
1409
+ }
1410
+ #watchAuthorized(watch) {
1411
+ const scope = watch.scope;
1412
+ this.#watchRoute(scope, watch.ownerRouteId, watch.routeReceipt);
1413
+ const policy = this.ctx.get('assistantPolicy', false);
1414
+ const actions = watch.taskFamily === undefined ? ['watch'] : ['watch', 'rollback'];
1415
+ if (!watch.routeReceipt || !this.#active || Date.now() >= watch.expiresAt || !policy
1416
+ || actions.some(action => policy.evaluate(this.#watchPolicy(scope, action)).effect !== 'allow'))
1417
+ throw new Error('assistant-skills: watch authority ended');
1418
+ }
1419
+ watch(agent, input) {
1420
+ const scope = this.#scope(agent, 'watch'), route = this.#watchRoute(scope, input.ownerRouteId);
1421
+ const policy = this.ctx.get('assistantPolicy', false);
1422
+ if (!policy || policy.evaluate(this.#watchPolicy(scope, 'watch')).effect !== 'allow')
1423
+ throw new Error('assistant-skills: configure finite background watch permission');
1424
+ this.#authorize(agent, 'watch', [scope, input]);
1425
+ this.#watchRoute(scope, input.ownerRouteId, route);
1426
+ return publicWatch(this.#store.createWatch(scope, input, route));
1427
+ }
1428
+ capture(agent, input) {
1429
+ const scope = this.#scope(agent, 'capture'), route = this.#watchRoute(scope, input.ownerRouteId);
1430
+ if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now() || input.expiresAt > Date.now() + 7 * 86400000)
1431
+ throw new Error('assistant-skills: invalid capture expiry');
1432
+ const policy = this.ctx.get('assistantPolicy', false);
1433
+ if (!policy || policy.evaluate(this.#capturePolicy(scope)).effect !== 'allow')
1434
+ throw new Error('assistant-skills: configure background capture permission');
1435
+ this.#authorize(agent, 'capture', [scope, input]);
1436
+ this.#watchRoute(scope, input.ownerRouteId, route);
1437
+ const goals = this.#goals();
1438
+ if (typeof goals.inspectActiveWorkflowCaptureContext !== 'function')
1439
+ throw new Error('assistant-skills: Goals active capture bridge unavailable');
1440
+ const active = goals.inspectActiveWorkflowCaptureContext(agent, input.goalId);
1441
+ const sessionId = String(agent.session.id);
1442
+ const goal = goals.inspectOwnerGoalExecution({ ownerRouteId: input.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId, goalId: input.goalId });
1443
+ const digest = goal.storedGoal?.definition?.digest, native = goal.storedGoal?.nativeAtLastObservation;
1444
+ if (acceptanceDigest(active.scope) !== acceptanceDigest(scope) || active.goalId !== input.goalId || active.sessionId !== sessionId
1445
+ || typeof active.nativeGoalId !== 'string' || typeof digest !== 'string' || !/^[a-f0-9]{64}$/u.test(digest)
1446
+ || active.definition.digest !== digest || native?.phase !== 'active' || native.sessionId !== active.sessionId || native.goalId !== active.nativeGoalId)
1447
+ throw new Error('assistant-skills: exact active goal required');
1448
+ this.#watchRoute(scope, input.ownerRouteId, route);
1449
+ const saved = this.#store.createCapture(scope, { ...input, sessionId, nativeGoalId: active.nativeGoalId }, route, digest);
1450
+ this.#queueReconcile();
1451
+ return publicCapture(saved);
1452
+ }
1453
+ #capturePolicy(scope) { return { subject: { kind: 'background', id: 'dsh-enhanced-assistant-skills', workspace: scope.workspace, principal: scope.principalId }, action: 'capture', resource, context: { initiator: 'background' } }; }
1454
+ #captureAuthorized(capture) {
1455
+ const scope = capture.scope;
1456
+ this.#watchRoute(scope, capture.ownerRouteId, capture.routeReceipt);
1457
+ const policy = this.ctx.get('assistantPolicy', false);
1458
+ if (!this.#active || Date.now() >= capture.expiresAt || !policy || policy.evaluate(this.#capturePolicy(scope)).effect !== 'allow'
1459
+ || policy.authorize(this.#capturePolicy(scope), { idempotencyKey: `${capture.id}:capture` }).effect !== 'allow')
1460
+ throw new Error('assistant-skills: capture authority ended');
1461
+ }
1462
+ #captureCurrent(capture) {
1463
+ const scope = capture.scope;
1464
+ this.#captureAuthorized(capture);
1465
+ const parent = this.#store.get(scope, capture.name);
1466
+ if ((parent?.version ?? 0) !== capture.parentVersion || (parent ? acceptanceDigest(parent) : null) !== capture.parentDigest)
1467
+ throw new Error('assistant-skills: capture changed');
1468
+ const snapshot = this.#goals().inspectOwnerGoalExecution({ ownerRouteId: capture.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: capture.sessionId, goalId: capture.goalId });
1469
+ const goal = snapshot.storedGoal;
1470
+ if (goal?.definition?.digest !== capture.definitionDigest || goal.nativeAtLastObservation?.sessionId !== capture.sessionId || goal.nativeAtLastObservation?.goalId !== capture.nativeGoalId)
1471
+ throw new Error('assistant-skills: capture changed');
1472
+ }
1473
+ #captureBridgeFailure(capture, error) {
1474
+ const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
1475
+ if (code === 'pending' || code === 'unavailable')
1476
+ return;
1477
+ const message = error instanceof Error ? error.message : '';
1478
+ const detail = typeof code === 'string' ? code : 'GOAL_CAPTURE_UNKNOWN';
1479
+ // Exact internal terminal outcomes are deliberately mapped without parsing
1480
+ // a foreign Host error message. All other failures become unknown once.
1481
+ const state = message === 'assistant-skills: owner route changed' || message === 'assistant-skills: capture authority ended' || message === 'assistant-skills: capture changed' ? 'revoked' : 'unknown';
1482
+ this.#store.finishCapture(capture.scope, capture.id, state, detail);
1483
+ }
1484
+ #queueReconcile() {
1485
+ if (!this.#active)
1486
+ return;
1487
+ this.#queueRepair();
1488
+ if (this.#reconcileQueued) {
1489
+ this.#reconcileDirty = true;
1490
+ return;
1491
+ }
1492
+ this.#reconcileQueued = true;
1493
+ queueMicrotask(() => {
1494
+ if (!this.#active) {
1495
+ this.#reconcileQueued = false;
1496
+ return;
1497
+ }
1498
+ ;
1499
+ const task = this.#reconcile().catch(() => { }).finally(() => { this.#reconcileQueued = false; });
1500
+ this.#captureTasks.add(task);
1501
+ void task.finally(() => { this.#captureTasks.delete(task); if (this.#reconcileDirty) {
1502
+ this.#reconcileDirty = false;
1503
+ this.#queueReconcile();
1504
+ } });
1505
+ });
1506
+ }
1507
+ async #reconcile() {
1508
+ if (!this.#active || !this.ctx.get('assistantGoals', false) || !this.ctx.get('assistantPolicy', false)
1509
+ || !this.ctx.get('assistantDelivery', false) || !this.ctx.get('assistantVerifier', false))
1510
+ return;
1511
+ // Establish current deployment authority before consuming any later Goal
1512
+ // outcome. A stale route/profile/policy cannot turn an old observation into
1513
+ // a promotion decision.
1514
+ for (const deployment of this.#store.listDeployments()) {
1515
+ const watch = this.#store.listWatches(deployment.scope).find(value => value.id === deployment.watchId);
1516
+ // These terminal facts cannot create a promotion, so synchronize them
1517
+ // without treating a later rollback as a policy revocation.
1518
+ if (deployment.expiresAt <= Date.now() || watch && ['rolled-back', 'expired', 'revoked', 'superseded'].includes(watch.state)) {
1519
+ this.#store.reconcileDeployment(deployment.scope, deployment.id);
1520
+ continue;
1521
+ }
1522
+ const current = this.#store.reconcileDeployment(deployment.scope, deployment.id);
1523
+ if (!current || current.state !== 'canary' && current.state !== 'promoted')
1524
+ continue;
1525
+ try {
1526
+ this.#deploymentAuthorized(current, true);
1527
+ }
1528
+ catch {
1529
+ this.#store.stopDeployment(current.scope, current.id, 'revoked');
1530
+ }
1531
+ }
1532
+ for (const watch of this.#store.listWatches()) {
1533
+ const scope = watch.scope;
1534
+ if (watch.expiresAt <= Date.now()) {
1535
+ this.#store.stopWatch(scope, watch.id, 'expired');
1536
+ continue;
1537
+ }
1538
+ const current = this.#store.get(scope, watch.skillName);
1539
+ if (!current || current.version !== watch.version || acceptanceDigest(current) !== watch.definitionDigest) {
1540
+ this.#store.stopWatch(scope, watch.id, 'superseded');
1541
+ continue;
1542
+ }
1543
+ try {
1544
+ this.#watchAuthorized(watch);
1545
+ }
1546
+ catch {
1547
+ this.#store.stopWatch(scope, watch.id, 'revoked');
1548
+ continue;
1549
+ }
1550
+ for (const runId of watch.runIds) {
1551
+ const run = this.#store.getRun(scope, runId);
1552
+ if (!run || run.skillName !== watch.skillName || run.version !== watch.version)
1553
+ continue;
1554
+ try {
1555
+ const read = () => this.#goals().inspectOwnerGoalExecution({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: run.sessionId, goalId: run.goalId });
1556
+ const currentDeployment = this.#store.listDeployments(scope).find(value => value.watchId === watch.id);
1557
+ const currentWatch = this.#store.listWatches(scope).find(value => value.id === watch.id);
1558
+ if (!currentWatch || currentWatch.state !== 'watching')
1559
+ break;
1560
+ const prior = currentDeployment && currentWatch.canonicalRevisions?.find(value => value.runId === run.id);
1561
+ const goals = this.#goals();
1562
+ if (!currentDeployment) {
1563
+ if (typeof goals.inspectOwnerGoalRunProof !== 'function') {
1564
+ this.#store.stopWatch(scope, watch.id, 'revoked');
1565
+ break;
1566
+ }
1567
+ const proof = await goals.inspectOwnerGoalRunProof({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: run.sessionId, goalId: run.goalId, runId: run.goalExecutionRunId }, this.#lifecycle.signal);
1568
+ const observation = watchObservation(read(), scope, run, Date.now(), proof);
1569
+ if (!observation)
1570
+ continue;
1571
+ this.#watchAuthorized(watch);
1572
+ if (acceptanceDigest(watchObservation(read(), scope, run, Date.now(), proof) ?? null) !== acceptanceDigest(observation))
1573
+ continue;
1574
+ const observed = this.#store.observeWatch(scope, watch.id, observation);
1575
+ if (observed?.state === 'watching' && observed.observations.length >= observed.maxRuns) {
1576
+ this.#store.stopWatch(scope, watch.id, 'exhausted');
1577
+ break;
1578
+ }
1579
+ continue;
1580
+ }
1581
+ // Deployment evidence is never downgraded to the immutable verifier
1582
+ // receipt. Goals proves owner/run/profile; Evaluation supplies the
1583
+ // current canonical revision for that exact assessment.
1584
+ if (!currentWatch.taskFamily || acceptanceDigest(currentDeployment.taskFamily) !== acceptanceDigest(currentWatch.taskFamily))
1585
+ continue;
1586
+ const evaluation = this.ctx.get('assistantEvaluation');
1587
+ if (!evaluation || typeof evaluation.getTrustedGoalOutcomeLearningProjection !== 'function'
1588
+ || typeof evaluation.withTrustedCanonicalTaskWriterFence !== 'function')
1589
+ continue;
1590
+ const proof = prior === undefined && typeof goals.inspectOwnerGoalRunProof === 'function'
1591
+ ? await goals.inspectOwnerGoalRunProof({ ownerRouteId: watch.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: run.sessionId, goalId: run.goalId, runId: run.goalExecutionRunId }, this.#lifecycle.signal)
1592
+ : undefined;
1593
+ if (prior === undefined && proof === undefined) {
1594
+ this.#store.stopWatch(scope, watch.id, 'revoked');
1595
+ break;
1596
+ }
1597
+ const first = prior === undefined ? read() : undefined;
1598
+ const assessments = first?.outcomeAssessments.filter(value => value.triggerRunId === run.goalExecutionRunId) ?? [];
1599
+ const task = assessments.length === 1 ? assessments[0]?.contract.task : undefined;
1600
+ const assessmentId = prior?.subjectRef ?? (task?.kind === 'goal-outcome' && task.ref === task.goal.assessmentId ? task.ref : undefined);
1601
+ if (!assessmentId)
1602
+ continue;
1603
+ const evaluationScope = evaluation.canonicalHostScope({ workspace: scope.workspace, preset: scope.preset });
1604
+ const canonical = evaluation.getTrustedGoalOutcomeLearningProjection({ scope: evaluationScope, assessmentId });
1605
+ if (!canonical || canonical.projection.subjectKind !== 'goal-outcome' || canonical.projection.subjectRef !== assessmentId)
1606
+ continue;
1607
+ const result = prior === undefined
1608
+ ? watchObservationResult(first, scope, run, Date.now(), proof, currentWatch.taskFamily, canonical, value => evaluation.isTrustedTaskLearningProjectionReceipt(value))
1609
+ : watchObservationRevision(prior.binding, scope, canonical, value => evaluation.isTrustedTaskLearningProjectionReceipt(value));
1610
+ if (!result)
1611
+ continue;
1612
+ if (prior !== undefined) {
1613
+ const firstCurrent = read();
1614
+ if (!watchBindingCurrent(firstCurrent, scope, run, currentWatch.taskFamily, prior.binding, canonical)
1615
+ || acceptanceDigest(read()) !== acceptanceDigest(firstCurrent))
1616
+ continue;
1617
+ }
1618
+ this.#watchAuthorized(watch);
1619
+ if (currentDeployment.state === 'canary' || currentDeployment.state === 'promoted')
1620
+ this.#deploymentAuthorized(currentDeployment);
1621
+ else if (currentDeployment.state === 'blocked')
1622
+ this.#deploymentCurrent(currentDeployment);
1623
+ else
1624
+ continue;
1625
+ if (prior === undefined && acceptanceDigest(watchObservationResult(read(), scope, run, Date.now(), proof, currentWatch.taskFamily, canonical, value => evaluation.isTrustedTaskLearningProjectionReceipt(value)) ?? null) !== acceptanceDigest(result))
1626
+ continue;
1627
+ const revision = result.kind === 'current' ? result.observation.canonical : result.canonical;
1628
+ const needsRollback = result.kind === 'invalidated' || result.observation.objectiveStatus === 'not-achieved';
1629
+ if (needsRollback) {
1630
+ const policy = this.ctx.get('assistantPolicy', false);
1631
+ if (policy.authorize(this.#watchPolicy(scope, 'rollback'), { idempotencyKey: `${watch.id}:rollback:${acceptanceDigest(revision)}` }).effect !== 'allow') {
1632
+ this.#store.stopWatch(scope, watch.id, 'revoked');
1633
+ break;
1634
+ }
1635
+ this.#watchAuthorized(watch);
1636
+ }
1637
+ const promotionFence = result.kind === 'current' && result.observation.objectiveStatus === 'achieved'
1638
+ ? this.#watchEvidenceCurrent(evaluation, scope, currentWatch, run, result, canonical) : undefined;
1639
+ if (result.kind === 'current' && result.observation.objectiveStatus === 'achieved' && !promotionFence)
1640
+ continue;
1641
+ const fence = promotionFence ?? { scope: evaluationScope, scopeWatermark: revision.scopeWatermark, evidence: [{
1642
+ subjectKind: revision.subjectKind, subjectRef: revision.subjectRef, version: revision.version, digest: revision.digest, disposition: revision.disposition,
1643
+ }] };
1644
+ const fenced = evaluation.withTrustedCanonicalTaskWriterFence(fence, () => {
1645
+ if (needsRollback) {
1646
+ const observed = this.#store.replaceWatchObservationAndRollback(scope, watch.id, result);
1647
+ return { observed, terminal: this.#store.getDeployment(scope, currentDeployment.id) };
1648
+ }
1649
+ const committed = this.#store.replaceWatchObservationAndPromote(scope, watch.id, currentDeployment.id, result);
1650
+ return { observed: committed.watch, terminal: committed.deployment };
1651
+ });
1652
+ if (!fenced.matched)
1653
+ continue;
1654
+ if (fenced.value.observed?.state === 'rolled-back' || fenced.value.terminal?.state !== currentDeployment.state)
1655
+ this.#changed();
1656
+ if (fenced.value.observed?.state === 'rolled-back')
1657
+ break;
1658
+ }
1659
+ catch { /* Changed or unavailable evidence supplies no rollback authority. */ }
1660
+ }
1661
+ }
1662
+ // Watches above produce only independently verified observations. Recheck
1663
+ // exact authority after that read, then let the store atomically select the
1664
+ // terminal deployment state; it never writes over a later skill version.
1665
+ for (const deployment of this.#store.listDeployments()) {
1666
+ const watch = this.#store.listWatches(deployment.scope).find(value => value.id === deployment.watchId);
1667
+ if (deployment.expiresAt <= Date.now() || watch && ['rolled-back', 'expired', 'revoked', 'superseded'].includes(watch.state)) {
1668
+ this.#store.reconcileDeployment(deployment.scope, deployment.id);
1669
+ continue;
1670
+ }
1671
+ const current = this.#store.reconcileDeployment(deployment.scope, deployment.id);
1672
+ if (!current || current.state !== 'canary' && current.state !== 'promoted')
1673
+ continue;
1674
+ // Promotion is committed only in the all-observation Evaluation fence
1675
+ // above. A bare reconciliation here could count stale prior successes.
1676
+ try {
1677
+ this.#deploymentAuthorized(current, true);
1678
+ }
1679
+ catch {
1680
+ this.#store.stopDeployment(current.scope, current.id, 'revoked');
1681
+ }
1682
+ }
1683
+ for (const capture of this.#store.listCaptures()) {
1684
+ const scope = capture.scope;
1685
+ if (capture.expiresAt <= Date.now()) {
1686
+ this.#store.finishCapture(scope, capture.id, 'expired');
1687
+ continue;
1688
+ }
1689
+ try {
1690
+ this.#captureCurrent(capture);
1691
+ const goals = this.#goals();
1692
+ if (typeof goals.inspectOwnerVerifiedWorkflowSource !== 'function') {
1693
+ this.#store.finishCapture(scope, capture.id, 'unsupported', 'Goals Host capture bridge unavailable');
1694
+ continue;
1695
+ }
1696
+ if (this.#captureInflight.has(capture.id)) {
1697
+ this.#captureDirty.add(capture.id);
1698
+ continue;
1699
+ }
1700
+ this.#captureInflight.add(capture.id);
1701
+ const task = goals.inspectOwnerVerifiedWorkflowSource({ ownerRouteId: capture.ownerRouteId, principalId: scope.principalId, workspace: scope.workspace, preset: scope.preset, sessionId: capture.sessionId, goalId: capture.goalId }, this.#lifecycle.signal).then(source => {
1702
+ try {
1703
+ this.#captureCurrent(capture);
1704
+ const definition = this.#capturedDefinition(source, { name: capture.name, description: capture.description }, scope);
1705
+ this.#captureCurrent(capture);
1706
+ this.#store.captureCandidate(scope, capture.id, definition);
1707
+ this.#changed();
1708
+ }
1709
+ catch (error) {
1710
+ this.#captureBridgeFailure(capture, error);
1711
+ }
1712
+ }).catch(error => this.#captureBridgeFailure(capture, error)).then(() => undefined);
1713
+ this.#captureTasks.add(task);
1714
+ void task.finally(() => { this.#captureTasks.delete(task); this.#captureInflight.delete(capture.id); if (this.#captureDirty.delete(capture.id) && this.#active)
1715
+ this.#queueReconcile(); });
1716
+ }
1717
+ catch (error) {
1718
+ this.#captureBridgeFailure(capture, error);
1719
+ }
1720
+ }
1721
+ }
1722
+ async run(exec, goalId, name, version, inputs, invocationId) {
1723
+ const scope = this.#scope(exec.agent, 'run');
1724
+ const skill = this.#store.get(scope, name);
1725
+ if (!skill || skill.retired || skill.version !== version)
1726
+ throw new Error('assistant-skills: active skill version required');
1727
+ return this.#run(exec, goalId, skill, inputs, invocationId);
1728
+ }
1729
+ async #run(exec, goalId, skill, inputs, invocationId, candidateId) {
1730
+ const action = candidateId ? 'trial' : 'run';
1731
+ const scope = this.#scope(exec.agent, action), { name, version } = skill;
1732
+ const definition = instantiate(skill, inputs);
1733
+ const steps = definition.steps;
1734
+ // These declarations are part of the immutable saved definition, rather
1735
+ // than an observation cache. Resolve them after input materialization so
1736
+ // a bound target is observed at the exact path that will be written.
1737
+ const observations = fileObservationSteps(definition);
1738
+ if (steps.length + observations.length > 32)
1739
+ throw new Error('assistant-skills: invalid file observation budget');
1740
+ const goals = this.#goals();
1741
+ let current;
1742
+ try {
1743
+ current = goals.inspectWorkflowRunContext(exec.agent, goalId);
1744
+ }
1745
+ catch (roundError) {
1746
+ // This is a handoff only: no durable invocation is claimed and no action
1747
+ // authorization budget is consumed until the next admitted native round.
1748
+ const invocationValid = typeof invocationId === 'string' && invocationId.length > 0 && invocationId.length <= 256 && !/[\p{Cc}]/u.test(invocationId);
1749
+ if (!invocationValid || typeof goals.inspectActiveWorkflowCaptureContext !== 'function')
1750
+ throw roundError;
1751
+ let active;
1752
+ try {
1753
+ active = goals.inspectActiveWorkflowCaptureContext(exec.agent, goalId);
1754
+ }
1755
+ catch {
1756
+ throw roundError;
1757
+ }
1758
+ if (acceptanceDigest(active.scope) !== acceptanceDigest(scope) || active.goalId !== goalId || active.sessionId !== String(exec.agent.session.id)
1759
+ || goalId === skill.source.goal.id || typeof active.nativeGoalId !== 'string' || typeof active.definition.digest !== 'string')
1760
+ throw roundError;
1761
+ exec.signal.throwIfAborted();
1762
+ this.#lifecycle.signal.throwIfAborted();
1763
+ if (acceptanceDigest(this.#scope(exec.agent, action)) !== acceptanceDigest(scope))
1764
+ throw roundError;
1765
+ exec.concludeTurn();
1766
+ const tool = candidateId ? 'skill_trial' : 'skill_run';
1767
+ return { state: 'awaiting-native-round', performed: false, context: 'No skill step or durable invocation has been claimed or executed; this is not a queued background task.', next: `After the Host starts this Goal's native round, call ${tool} again with this same goal, skill or candidate, inputs, and invocation_id.`, goalId, ...(candidateId ? { candidateId } : {}), skillName: name, version, invocationId, inputs };
1768
+ }
1769
+ if (acceptanceDigest(scope) !== acceptanceDigest(current.scope) || goalId === skill.source.goal.id)
1770
+ throw new Error('assistant-skills: fresh owner Goal required');
1771
+ const identity = acceptanceDigest(current);
1772
+ const goalContext = current;
1773
+ // claim() reconciles a deployed version while holding its transaction. Do
1774
+ // not let that implicit state transition promote from an old background
1775
+ // permission or route observation.
1776
+ const deploymentBeforeClaim = candidateId ? undefined : this.#store.deploymentForVersion(scope, name, version);
1777
+ const priorRun = this.#store.getRun(scope, `skill-run-${acceptanceDigest([scope, current.sessionId, invocationId])}`);
1778
+ if (deploymentBeforeClaim && !priorRun) {
1779
+ // Evaluation is an optional plugin for manual skills, but a qualified
1780
+ // deployment may not admit new work while its canonical evidence cannot
1781
+ // be read. Provider unload is transient and therefore does not revoke the
1782
+ // durable deployment; dispatch simply fails closed before claim.
1783
+ this.#deploymentCanonicalEvidenceCurrent(deploymentBeforeClaim);
1784
+ try {
1785
+ this.#deploymentAuthorized(deploymentBeforeClaim, true);
1786
+ const watch = this.#store.listWatches(scope).find(value => value.id === deploymentBeforeClaim.watchId);
1787
+ if (!watch)
1788
+ throw new Error('assistant-skills: deployment watch unavailable');
1789
+ this.#watchAuthorized(watch);
1790
+ }
1791
+ catch {
1792
+ this.#store.stopDeployment(scope, deploymentBeforeClaim.id, 'revoked');
1793
+ throw new Error('assistant-skills: deployment authority ended');
1794
+ }
1795
+ }
1796
+ const claim = this.#store.claim(scope, { invocationId, goalId, sessionId: current.sessionId, skillName: name, version, inputs, goalExecutionRunId: current.goalExecutionRunId, goalDefinitionDigest: current.definition.digest, ...(typeof goalContext.nativeGoalId === 'string' ? { nativeGoalId: goalContext.nativeGoalId } : {}), ...(candidateId ? { candidateId } : {}) });
1797
+ if (!claim.claimed) {
1798
+ if (claim.run.state !== 'succeeded')
1799
+ throw new Error(`assistant-skills: invocation ${claim.run.id} is ${claim.run.state}; inspect skill_status, do not replay`);
1800
+ return { ...claim.run, replayed: false, acceptance: 'requires-fresh-goal-verification' };
1801
+ }
1802
+ // claim() atomically reserves a deployment slot with the run. The second
1803
+ // read binds this dispatch to that exact reservation before any tool call.
1804
+ const deployment = deploymentBeforeClaim;
1805
+ if (deployment) {
1806
+ try {
1807
+ const reserved = this.#store.assertDeploymentRun(scope, claim.run.id);
1808
+ this.#deploymentCanonicalEvidenceCurrent(reserved);
1809
+ this.#deploymentAuthorized(reserved);
1810
+ }
1811
+ catch {
1812
+ this.#store.finish(scope, claim.run.id, 'unknown', []);
1813
+ throw new Error(`assistant-skills: invocation ${claim.run.id} is unknown; inspect skill_status, do not replay`);
1814
+ }
1815
+ }
1816
+ const deploymentRevision = deployment ? new AbortController() : undefined;
1817
+ if (deploymentRevision)
1818
+ this.#deployedExecutions.add(deploymentRevision);
1819
+ const timeout = new AbortController();
1820
+ const timer = setTimeout(() => timeout.abort(), this.#duration);
1821
+ timer.unref?.();
1822
+ const signal = AbortSignal.any([exec.signal, timeout.signal, this.#lifecycle.signal, ...(deploymentRevision ? [deploymentRevision.signal] : [])]);
1823
+ const completed = [];
1824
+ let state = 'unknown';
1825
+ let dispatched = false;
1826
+ const revalidate = () => {
1827
+ signal.throwIfAborted();
1828
+ if (acceptanceDigest(this.#scope(exec.agent, action)) !== acceptanceDigest(scope)
1829
+ || acceptanceDigest(this.#goals().inspectWorkflowRunContext(exec.agent, goalId)) !== identity)
1830
+ throw new Error('assistant-skills: current authority changed');
1831
+ if (this.#store.getRun(scope, claim.run.id)?.state !== 'running')
1832
+ throw new Error('assistant-skills: invocation no longer owns dispatch');
1833
+ if (candidateId)
1834
+ this.#pending(scope, candidateId);
1835
+ else {
1836
+ const live = this.#store.get(scope, name);
1837
+ if (!live || live.retired || live.version !== version)
1838
+ throw new Error('assistant-skills: skill retired or superseded');
1839
+ if (deployment) {
1840
+ const reserved = this.#store.assertDeploymentRun(scope, claim.run.id);
1841
+ this.#deploymentCanonicalEvidenceCurrent(reserved);
1842
+ this.#deploymentAuthorized(reserved);
1843
+ }
1844
+ }
1845
+ };
1846
+ try {
1847
+ this.#authorize(exec.agent, action, claim.run.id);
1848
+ stepsLoop: for (const [index, step] of steps.entries()) {
1849
+ for (const observation of observations) {
1850
+ if (observation.beforeStepId !== step.id)
1851
+ continue;
1852
+ revalidate();
1853
+ if (!this.#allowed.includes('read'))
1854
+ throw new Error('assistant-skills: current tool allowlist denied required file observation');
1855
+ dispatched = true;
1856
+ const observed = await exec.agent.ctx.get('tools').execute({
1857
+ callId: ToolCallId(`${exec.callId}:skill-observation:${index + 1}`),
1858
+ rootCallId: exec.rootCallId,
1859
+ parent: exec.token,
1860
+ agent: exec.agent,
1861
+ name: 'read',
1862
+ arguments: { file_path: observation.filePath, limit: 1 },
1863
+ signal,
1864
+ });
1865
+ revalidate();
1866
+ for (const context of observed.additionalContexts ?? [])
1867
+ exec.deferContext(context);
1868
+ const code = observed.isError ? observed.error.info?.code ?? 'tool-rejected' : undefined;
1869
+ const interrupted = signal.aborted || code === 'ABORTED' || code === 'ABORTED_BEFORE_DISPATCH';
1870
+ const absent = observation.allowAbsent && step.toolName === 'write' && code === 'FS_NOT_FOUND';
1871
+ const detail = absent ? 'absence:FS_NOT_FOUND' : observed.isError ? code : `result:${acceptanceDigest(observed.content)}`;
1872
+ completed.push({
1873
+ id: observation.id,
1874
+ state: interrupted ? 'unknown' : observed.isError && !absent ? 'failed' : 'succeeded',
1875
+ ...(detail === undefined ? {} : { detail }),
1876
+ });
1877
+ this.#store.checkpoint(scope, claim.run.id, completed);
1878
+ if (interrupted) {
1879
+ state = 'unknown';
1880
+ break stepsLoop;
1881
+ }
1882
+ if (observed.isError && !absent) {
1883
+ state = 'failed';
1884
+ break stepsLoop;
1885
+ }
1886
+ revalidate();
1887
+ dispatched = false;
1888
+ }
1889
+ revalidate();
1890
+ if (!this.#allowed.includes(step.toolName))
1891
+ throw new Error('assistant-skills: current tool allowlist denied');
1892
+ dispatched = true;
1893
+ const result = await exec.agent.ctx.get('tools').execute({ callId: ToolCallId(`${exec.callId}:skill:${index + 1}`), rootCallId: exec.rootCallId, parent: exec.token, agent: exec.agent, name: step.toolName, arguments: step.arguments, signal });
1894
+ revalidate();
1895
+ for (const context of result.additionalContexts ?? [])
1896
+ exec.deferContext(context);
1897
+ const interrupted = signal.aborted || result.isError && ['ABORTED', 'ABORTED_BEFORE_DISPATCH'].includes(result.error.info?.code ?? '');
1898
+ completed.push({ id: step.id, state: interrupted ? 'unknown' : result.isError ? 'failed' : 'succeeded', detail: result.isError ? result.error.info?.code ?? 'tool-rejected' : `result:${acceptanceDigest(result.content)}` });
1899
+ this.#store.checkpoint(scope, claim.run.id, completed);
1900
+ if (result.isError || interrupted) {
1901
+ state = interrupted ? 'unknown' : 'failed';
1902
+ break;
1903
+ }
1904
+ if (result.concludesTurn)
1905
+ exec.concludeTurn();
1906
+ revalidate();
1907
+ dispatched = false;
1908
+ if (index === steps.length - 1)
1909
+ state = 'succeeded';
1910
+ }
1911
+ }
1912
+ catch {
1913
+ state = deployment || dispatched || signal.aborted ? 'unknown' : 'failed';
1914
+ }
1915
+ finally {
1916
+ clearTimeout(timer);
1917
+ if (deploymentRevision)
1918
+ this.#deployedExecutions.delete(deploymentRevision);
1919
+ }
1920
+ if (!this.#active)
1921
+ throw new Error('assistant-skills: runtime disposed; invocation will recover as unknown');
1922
+ const saved = this.#store.finish(scope, claim.run.id, state, completed);
1923
+ if (saved.state !== 'succeeded')
1924
+ throw new Error(`assistant-skills: invocation ${saved.id} is ${saved.state}; inspect skill_status, do not replay`);
1925
+ this.#queueReconcile();
1926
+ return { ...saved, replayed: false, acceptance: 'requires-fresh-goal-verification' };
1927
+ }
1928
+ }
1929
+ //# sourceMappingURL=service.js.map