openpond-sdk 0.5.6 → 0.5.8

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 (44) hide show
  1. package/TRAINING_PROTOCOL.md +32 -0
  2. package/dist/chunk-ARUJ3FJT.js +447 -0
  3. package/dist/chunk-ARUJ3FJT.js.map +7 -0
  4. package/dist/chunk-QFT2MCIC.js +1526 -0
  5. package/dist/chunk-QFT2MCIC.js.map +7 -0
  6. package/dist/{chunk-NF63YMOL.js → chunk-QUOW6URQ.js} +7 -9
  7. package/dist/{chunk-NF63YMOL.js.map → chunk-QUOW6URQ.js.map} +3 -3
  8. package/dist/{chunk-25TBEGXB.js → chunk-TKJU3BOW.js} +2 -2
  9. package/dist/chunk-UJZ3XEE3.js +902 -0
  10. package/dist/chunk-UJZ3XEE3.js.map +7 -0
  11. package/dist/{chunk-EV4PKJWN.js → chunk-UKVK7MRJ.js} +320 -208
  12. package/dist/chunk-UKVK7MRJ.js.map +7 -0
  13. package/dist/index.js +3 -3
  14. package/dist/learning.js +3 -3
  15. package/dist/model-taskset-runs.js +2 -2
  16. package/dist/refiner.js +1 -1
  17. package/dist/taskset-drafts.js +35 -33
  18. package/dist/taskset-drafts.js.map +1 -1
  19. package/dist/taskset-packages.js +51 -465
  20. package/dist/taskset-packages.js.map +4 -4
  21. package/dist/training-bundle.js +619 -0
  22. package/dist/training-bundle.js.map +7 -0
  23. package/dist/types/packages/sdk/src/managed-training-preparation.d.ts +201 -0
  24. package/dist/types/packages/sdk/src/managed-training-preparation.d.ts.map +1 -0
  25. package/dist/types/packages/sdk/src/model-projects.d.ts +16 -16
  26. package/dist/types/packages/sdk/src/taskset-authored-contracts.d.ts +2 -2
  27. package/dist/types/packages/sdk/src/taskset-authored-files.d.ts +1 -1
  28. package/dist/types/packages/sdk/src/taskset-draft-client.d.ts +2 -2
  29. package/dist/types/packages/sdk/src/taskset-draft-dataset-artifacts.d.ts +2 -2
  30. package/dist/types/packages/sdk/src/taskset-package-client.d.ts +2 -2
  31. package/dist/types/packages/sdk/src/taskset-package-projection.d.ts +1 -1
  32. package/dist/types/packages/sdk/src/training-bundle-contracts.d.ts +287 -0
  33. package/dist/types/packages/sdk/src/training-bundle-contracts.d.ts.map +1 -0
  34. package/dist/types/packages/sdk/src/training-bundle.d.ts +50 -0
  35. package/dist/types/packages/sdk/src/training-bundle.d.ts.map +1 -0
  36. package/dist/types/packages/sdk/src/training-learning-batch.d.ts +1428 -0
  37. package/dist/types/packages/sdk/src/training-learning-batch.d.ts.map +1 -0
  38. package/dist/types/packages/sdk/src/training-privacy.d.ts +8 -0
  39. package/dist/types/packages/sdk/src/training-privacy.d.ts.map +1 -0
  40. package/package.json +6 -2
  41. package/dist/chunk-EV4PKJWN.js.map +0 -7
  42. package/dist/chunk-PXB6YY36.js +0 -2395
  43. package/dist/chunk-PXB6YY36.js.map +0 -7
  44. /package/dist/{chunk-25TBEGXB.js.map → chunk-TKJU3BOW.js.map} +0 -0
@@ -0,0 +1,619 @@
1
+ import {
2
+ learningVerifierModule,
3
+ projectLearningBatchGraders,
4
+ resolvePortableTasksetRewardExecution
5
+ } from "./chunk-ARUJ3FJT.js";
6
+ import {
7
+ LearningBatchDatasetSourceRefSchema,
8
+ TasksetSchema,
9
+ computeTasksetHash
10
+ } from "./chunk-QFT2MCIC.js";
11
+ import "./chunk-4IDTRFW2.js";
12
+ import "./chunk-S7I45WJQ.js";
13
+ import {
14
+ TRAINING_EVALUATION_SOURCE_PATH,
15
+ TrainingEvaluationSourceSchema,
16
+ assertTrainingEvaluationIsolation,
17
+ parseAndVerifyTrainingInputArtifactUpload,
18
+ parseAndVerifyTrainingJobSubmission,
19
+ trainingEvaluationSourceRef,
20
+ trainingInputArtifactUploadHash,
21
+ trainingJobSubmissionHash
22
+ } from "./chunk-F5JBFRAH.js";
23
+ import {
24
+ HarnessSourceSelectionSchema,
25
+ validateHarnessSourcePackage
26
+ } from "./chunk-UKVK7MRJ.js";
27
+ import {
28
+ ImmutableReleaseRefSchema,
29
+ ReleaseHashSchema,
30
+ ReleaseIdSchema,
31
+ ReleaseTimestampSchema,
32
+ canonicalJson,
33
+ contentHash,
34
+ sha256
35
+ } from "./chunk-QUOW6URQ.js";
36
+ import "./chunk-ZNFOVV7B.js";
37
+
38
+ // src/training-bundle.ts
39
+ import { compileBoundGraders } from "@openpond/evals/rewards";
40
+ import { verifyLearningTextAsset as verifyLearningTextAsset2 } from "@openpond/evals/learning";
41
+
42
+ // src/training-bundle-contracts.ts
43
+ import { z } from "zod";
44
+ var ScopedSecretDeclarationSchema = z.object({
45
+ id: ReleaseIdSchema,
46
+ purpose: z.string().trim().min(1).max(500),
47
+ audience: z.enum([
48
+ "orchestrator",
49
+ "environment",
50
+ "privileged_scorer",
51
+ "trainer",
52
+ "infrastructure"
53
+ ]),
54
+ required: z.boolean(),
55
+ ttlSeconds: z.number().int().positive().max(86400),
56
+ scopes: z.array(z.string().trim().min(1).max(500)).max(100).default([])
57
+ }).strict();
58
+ var OpaqueSecretLeaseRefSchema = z.object({
59
+ declarationId: ReleaseIdSchema,
60
+ leaseRef: z.string().trim().min(8).max(1e3),
61
+ audience: ScopedSecretDeclarationSchema.shape.audience,
62
+ expiresAt: ReleaseTimestampSchema
63
+ }).strict();
64
+ var HarnessRuntimeTargetBindingSchema = z.object({
65
+ adapterId: ReleaseIdSchema,
66
+ placement: z.enum(["local", "remote", "colocated", "provider_native"]),
67
+ capabilityReceipt: ReleaseHashSchema,
68
+ runtimeVersion: z.string().trim().min(1).max(200),
69
+ dataPlane: z.object({
70
+ provider: ReleaseIdSchema,
71
+ dataPlaneId: ReleaseIdSchema,
72
+ cellId: ReleaseIdSchema,
73
+ runnerPoolId: ReleaseIdSchema,
74
+ runtimeImageDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/),
75
+ capabilityReceipt: ReleaseHashSchema
76
+ }).strict().nullable()
77
+ }).strict();
78
+ var ComputeTargetBindingSchema = z.object({
79
+ adapterId: ReleaseIdSchema,
80
+ kind: z.enum(["local", "ssh", "managed", "custom"]),
81
+ deviceOrPool: z.string().trim().min(1).max(1e3),
82
+ capabilityReceipt: ReleaseHashSchema,
83
+ provider: ReleaseIdSchema.nullable()
84
+ }).strict();
85
+ var TrainingEngineBindingSchema = z.object({
86
+ adapterId: ReleaseIdSchema,
87
+ workerVersion: z.string().trim().min(1).max(200),
88
+ workerImageDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/).nullable(),
89
+ upstreamRevision: z.string().trim().min(1).max(500),
90
+ capabilityReceipt: ReleaseHashSchema
91
+ }).strict();
92
+ var HarnessRunManifestContentSchema = z.object({
93
+ schemaVersion: z.literal("openpond.harnessRunManifest.v1"),
94
+ id: ReleaseIdSchema,
95
+ harnessRelease: ImmutableReleaseRefSchema,
96
+ datasetRelease: ImmutableReleaseRefSchema,
97
+ evidenceSets: z.array(ImmutableReleaseRefSchema).max(1e4),
98
+ model: z.object({
99
+ source: z.string().trim().min(1).max(200),
100
+ revision: z.string().trim().min(1).max(500),
101
+ artifactHash: ReleaseHashSchema.nullable(),
102
+ tokenizerRevision: z.string().trim().min(1).max(500),
103
+ chatTemplateHash: ReleaseHashSchema
104
+ }).strict(),
105
+ recipe: z.object({
106
+ method: z.string().trim().min(1).max(100),
107
+ version: z.string().trim().min(1).max(200),
108
+ configHash: ReleaseHashSchema
109
+ }).strict(),
110
+ runtimeTarget: HarnessRuntimeTargetBindingSchema,
111
+ computeTarget: ComputeTargetBindingSchema,
112
+ engine: TrainingEngineBindingSchema,
113
+ resolvedBundleHash: ReleaseHashSchema,
114
+ secretLeaseRefs: z.array(OpaqueSecretLeaseRefSchema).max(1e3),
115
+ approval: z.object({
116
+ approvalHash: ReleaseHashSchema,
117
+ approvedAt: ReleaseTimestampSchema,
118
+ maximumSpendUsd: z.number().nonnegative().nullable()
119
+ }).strict(),
120
+ createdAt: ReleaseTimestampSchema
121
+ }).strict();
122
+ var HarnessRunManifestSchema = HarnessRunManifestContentSchema.extend({
123
+ contentHash: ReleaseHashSchema
124
+ }).strict();
125
+ var ResolvedTrainingBundleContentSchema = z.object({
126
+ schemaVersion: z.literal("openpond.resolvedTrainingBundle.v1"),
127
+ projection: z.literal("trainer"),
128
+ harnessRelease: ImmutableReleaseRefSchema,
129
+ datasetRelease: ImmutableReleaseRefSchema,
130
+ evidenceSetRelease: ImmutableReleaseRefSchema.nullable(),
131
+ files: z.array(
132
+ z.object({
133
+ path: z.string().trim().min(1).max(2e3),
134
+ sha256: ReleaseHashSchema,
135
+ sizeBytes: z.number().int().nonnegative()
136
+ }).strict()
137
+ ).max(1e5)
138
+ }).strict();
139
+ var ResolvedTrainingBundleManifestSchema = ResolvedTrainingBundleContentSchema.extend({
140
+ contentHash: ReleaseHashSchema
141
+ }).strict();
142
+
143
+ // src/training-privacy.ts
144
+ var SECRET_PATTERNS = [
145
+ { label: "OpenAI-style API key", pattern: /\bsk-[A-Za-z0-9_-]{16,}\b/g },
146
+ { label: "OpenPond API key", pattern: /\bopk_[A-Za-z0-9_-]{12,}\b/g },
147
+ { label: "private key", pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g },
148
+ { label: "credential assignment", pattern: /\b(?:api[_-]?key|password|secret|token)\s*[:=]\s*["']?[^\s"']{8,}/gi }
149
+ ];
150
+ var EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
151
+ var PHONE_PATTERN = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/g;
152
+ function scanAndRedactEvidence(text) {
153
+ const findings = [];
154
+ let redacted = text;
155
+ for (const item of SECRET_PATTERNS) {
156
+ item.pattern.lastIndex = 0;
157
+ if (item.pattern.test(redacted)) findings.push(item.label);
158
+ item.pattern.lastIndex = 0;
159
+ redacted = redacted.replace(item.pattern, `[REDACTED ${item.label}]`);
160
+ }
161
+ EMAIL_PATTERN.lastIndex = 0;
162
+ const hasEmail = EMAIL_PATTERN.test(redacted);
163
+ EMAIL_PATTERN.lastIndex = 0;
164
+ if (hasEmail) findings.push("email address");
165
+ redacted = redacted.replace(EMAIL_PATTERN, "[REDACTED email]");
166
+ PHONE_PATTERN.lastIndex = 0;
167
+ const hasPhone = PHONE_PATTERN.test(redacted);
168
+ PHONE_PATTERN.lastIndex = 0;
169
+ if (hasPhone) findings.push("phone number");
170
+ redacted = redacted.replace(PHONE_PATTERN, "[REDACTED phone]");
171
+ return {
172
+ secretStatus: findings.some((finding) => !finding.includes("email") && !finding.includes("phone")) ? "blocked" : "passed",
173
+ piiStatus: hasEmail || hasPhone ? "review" : "passed",
174
+ findings,
175
+ redacted
176
+ };
177
+ }
178
+
179
+ // src/training-learning-batch.ts
180
+ import { compileTaskBatch, learningRef, taskBatchPackageMetadata, verifyLearningTextAsset } from "@openpond/evals";
181
+ function materializeLearningBatchTaskset(input) {
182
+ const release = compileTaskBatch(input);
183
+ const metadata = taskBatchPackageMetadata(release);
184
+ if (release.environment.kind !== "text" || release.tools.length || release.tasks.some((task) => task.artifactRefs.length)) throw new Error("This learning batch requires an environment or asset adapter before training preparation.");
185
+ const sourceId = input.source.id;
186
+ const signals = { demonstrations: [], preferences: [], corrections: [], feedback: [], rewards: [], labels: [] };
187
+ const fixtures = [];
188
+ for (const admission of metadata.admissions) {
189
+ const evidence = input.evidence.find((item) => item.id === admission.evidence.id);
190
+ const decision = input.decisions.find((item) => item.id === admission.decision.id);
191
+ if (input.batch.purpose === "reward_training" && decision.grade?.training.status === "scored") signals.rewards.push({
192
+ id: `reward-${admission.taskId}`,
193
+ taskId: admission.taskId,
194
+ kind: "reward",
195
+ task: input.definition.instructions,
196
+ rules: [{ id: input.binding.id, points: 1, condition: "Execute the released Reward binding with its declared normalization, weights and gates." }],
197
+ otherwisePoints: 0,
198
+ executable: true,
199
+ approved: true,
200
+ confidence: 1,
201
+ sourceRefs: [sourceId],
202
+ artifactRef: input.binding.id,
203
+ metadata: { rewardBinding: learningRef(input.binding), evidence: admission.evidence, decision: admission.decision }
204
+ });
205
+ if (admission.supervisedTarget !== null) signals.demonstrations.push({ kind: "demonstration", id: `demo-${admission.taskId}`, taskId: admission.taskId, sourceRefs: [sourceId], artifactRef: admission.decision.id, approved: true, confidence: 1, prompt: null, response: JSON.stringify(admission.supervisedTarget), metadata: { evidence: admission.evidence, decision: admission.decision, targetGradeHash: decision.targetGrade?.contentHash } });
206
+ for (const [kind, output, grade] of [["observed", evidence.submission.observedOutput, decision.grade], ["target", decision.approvedTarget, decision.targetGrade]]) {
207
+ const outcome = fixtureOutcome(grade);
208
+ if (output === null || outcome === null) continue;
209
+ fixtures.push({ id: `fixture-${kind}-${admission.taskId}`, taskId: admission.taskId, label: outcome.passed ? "positive" : "negative", output, infrastructureError: null, expectedPassed: outcome.passed, expectedRewardEligible: outcome.rewardEligible, metadata: { gradeReceiptHash: grade.contentHash, evidence: admission.evidence, decision: admission.decision } });
210
+ }
211
+ }
212
+ if (!fixtures.length) throw new Error("Grade at least one reviewed response before preparing this batch. Grader fixtures must come from actual receipts.");
213
+ const graders = projectLearningBatchGraders(input.binding, input.rewards, input.assets);
214
+ const generatedFiles = [];
215
+ for (const grader of release.graders) {
216
+ if (grader.kind !== "custom_verifier") continue;
217
+ const asset = input.assets?.find((asset2) => asset2.id === grader.verifierRef.id);
218
+ if (!asset) throw new Error(`Reward ${grader.id} is missing its immutable verifier source.`);
219
+ const module = learningVerifierModule(grader.verifierRef.contentHash);
220
+ if (!generatedFiles.some((file) => file.path === module)) generatedFiles.push({ path: module, role: "verifier", content: verifyLearningTextAsset(asset, grader.verifierRef) });
221
+ }
222
+ const taskset = TasksetSchema.parse({
223
+ schemaVersion: "openpond.taskset.v1",
224
+ id: `learning-${contentHash([input.profileId, input.batch.contentHash]).slice(0, 40)}`,
225
+ revision: 1,
226
+ profileId: input.profileId,
227
+ profileRelease: null,
228
+ createImproveRunId: null,
229
+ name: `${input.definition.name} \xB7 ${input.batch.examples.length} approved examples`,
230
+ objective: input.definition.instructions,
231
+ purpose: "general",
232
+ benchmark: null,
233
+ status: "needs_review",
234
+ sourceRefs: [input.source],
235
+ datasetArtifact: null,
236
+ policy: release.policy,
237
+ environment: { protocolVersion: "openpond.taskEnvironment.v1", kind: "chat", entrypoint: "openpond.text.v1", stateful: false, deterministicSeeds: release.environment.deterministicSeeds, toolNames: [], lifecycle: ["create", "reset", "step", "grade", "cleanup"], defaultTimeoutMs: release.environment.defaultTimeoutMs, networkPolicy: release.environment.networkPolicy, metadata: { portableEnvironment: release.environment } },
238
+ capabilities: { schemaVersion: "openpond.tasksetCapabilities.v1", taskKind: "chat", supportedSignals: signals.demonstrations.length ? ["demonstration", "reward"] : ["reward"], compatibleMethods: input.batch.purpose === "supervised_training" ? ["sft"] : input.batch.purpose === "reward_training" ? ["grpo", "ppo"] : ["none"], rewardKinds: ["deterministic"], requiresTools: false, requiresState: false, requiresPrivilegedGrading: true, environmentPlacements: ["local", "remote"], exportable: true, portabilityBlockers: [] },
239
+ tasks: release.tasks.map(({ artifactRefs: _assets, ...task }) => ({ ...task, schemaVersion: "openpond.taskData.v1", sourceRefs: [sourceId], metadata: { admission: metadata.admissions.find((entry) => entry.taskId === task.id), exampleOrigin: input.evidence.find((evidence) => evidence.id === task.id)?.correctionFeedbackId ? "corrected" : "extracted" } })),
240
+ graders,
241
+ graderFixtures: fixtures,
242
+ learningSignals: signals,
243
+ authoringProvenance: { schemaVersion: "openpond.taskAuthoringProvenance.v1", model: null, modelConfig: {}, skillHash: contentHash("openpond-learning-batch-v1"), promptTemplateVersion: "learning-batch-v1", buildIntent: input.batch.purpose === "supervised_training" ? "demonstrations" : "verifiable_reward", buildSpecification: null, evidenceHashes: input.batch.examples.map((entry) => entry.evidence.contentHash), tasksetSdkVersion: "learning-batch-v1", sourceCommit: null, repairHistory: [], createdAt: input.batch.sealedAt },
244
+ readiness: null,
245
+ contentHash: "00000000",
246
+ createdAt: input.batch.sealedAt,
247
+ updatedAt: input.batch.sealedAt,
248
+ metadata: { learning: metadata, learningRelease: learningRef(release), trainingMethod: input.batch.purpose === "supervised_training" ? "sft" : input.batch.purpose === "reward_training" ? "grpo" : "none", tasksetOutputContract: { mode: "structured_json", jsonSchema: input.definition.outputSchema, renderer: null } }
249
+ });
250
+ return { taskset: TasksetSchema.parse({ ...taskset, contentHash: computeTasksetHash(taskset) }), release, generatedFiles };
251
+ }
252
+ function fixtureOutcome(grade) {
253
+ if (!grade) return null;
254
+ const outcome = grade.training.status === "not_configured" ? grade.evaluation : grade.training;
255
+ if (outcome.status !== "scored") return null;
256
+ return { passed: outcome.passed === true, rewardEligible: grade.training.status === "scored" };
257
+ }
258
+ function prepareReviewedLearningBatch(input) {
259
+ const scan = scanAndRedactEvidence(JSON.stringify({
260
+ tasks: input.evidence.map((item) => item.submission),
261
+ targets: input.decisions.map((item) => item.approvedTarget),
262
+ definition: input.definition
263
+ }));
264
+ if (scan.secretStatus !== "passed" || scan.piiStatus !== "passed") throw new Error(`Learning batch contains unresolved data findings (${scan.findings.join(", ")}). Correct the source examples and seal a revised batch before training.`);
265
+ const source = LearningBatchDatasetSourceRefSchema.parse({
266
+ schemaVersion: "openpond.learningBatchDatasetSource.v1",
267
+ kind: "learning_batch",
268
+ id: `batch-source-${input.batch.contentHash.slice(0, 40)}`,
269
+ profileId: input.profileId,
270
+ title: input.definition.name,
271
+ sourceHash: input.batch.contentHash,
272
+ occurredAt: input.batch.sealedAt,
273
+ batch: learningRef(input.batch),
274
+ taskDefinition: input.batch.taskDefinition,
275
+ admittedBy: input.batch.sealedBy,
276
+ licensingStatus: "approved",
277
+ secretScanStatus: scan.secretStatus,
278
+ piiScanStatus: scan.piiStatus,
279
+ metadata: { admission: input.admissionContext ?? "explicit_local_batch_review", privacyScanner: "openpond-evidence-v1", findings: scan.findings }
280
+ });
281
+ return materializeLearningBatchTaskset({ ...input, source });
282
+ }
283
+
284
+ // src/managed-training-preparation.ts
285
+ import { z as z2 } from "zod";
286
+ async function prepareManagedTrainingSubmission(raw) {
287
+ const input = structuredClone(raw);
288
+ const { project, recipe, taskset, approval } = input;
289
+ const manifest = HarnessRunManifestSchema.parse(input.manifest);
290
+ const bundle = ResolvedTrainingBundleManifestSchema.parse(input.bundleManifest);
291
+ const { contentHash: manifestHash, ...manifestContent } = manifest;
292
+ const { contentHash: bundleHash, ...bundleContent } = bundle;
293
+ if (contentHash(manifestContent) !== manifestHash || contentHash(bundleContent) !== bundleHash || manifest.resolvedBundleHash !== bundleHash || contentHash(manifest.harnessRelease) !== contentHash(bundle.harnessRelease) || contentHash(manifest.datasetRelease) !== contentHash(bundle.datasetRelease) || contentHash(manifest.evidenceSets) !== contentHash(bundle.evidenceSetRelease ? [bundle.evidenceSetRelease] : [])) {
294
+ throw new Error("The managed training manifest or resolved bundle changed.");
295
+ }
296
+ if (!project.hosted || project.hosted.syncedSourceRevision !== project.revision || project.hosted.portableProjectId !== project.id) throw new Error("Sync the exact Model Project revision before preparing managed training.");
297
+ const baseModel = project.trainingSetup.baseModel;
298
+ if (!baseModel || baseModel.modelId !== manifest.model.source || baseModel.revision !== manifest.model.revision || baseModel.tokenizerRevision !== manifest.model.tokenizerRevision || baseModel.chatTemplateHash !== manifest.model.chatTemplateHash) {
299
+ throw new Error("The synced Model Project base Model does not match the Run manifest.");
300
+ }
301
+ if (recipe.method !== "grpo" || contentHash(recipe) !== manifest.recipe.configHash || approval.approvalHash !== manifest.approval.approvalHash || approval.maximumSpendUsd !== manifest.approval.maximumSpendUsd) {
302
+ throw new Error("Managed training requires the exact approved GRPO recipe and spend ceiling.");
303
+ }
304
+ const limits = z2.object({ wallTimeMs: z2.number().positive() }).parse(recipe.resourceLimits);
305
+ if (input.idempotencyKey !== void 0) z2.string().trim().min(16).max(191).parse(input.idempotencyKey);
306
+ const files = verifyFiles(bundle, input.files);
307
+ const evaluationSource = TrainingEvaluationSourceSchema.parse(input.evaluationSource);
308
+ const evaluationFile = files.find((file) => file.path === TRAINING_EVALUATION_SOURCE_PATH);
309
+ if (!evaluationFile || contentHash(JSON.parse(Buffer.from(evaluationFile.content, "base64").toString("utf8"))) !== contentHash(evaluationSource)) {
310
+ throw new Error("The evaluation source differs from the immutable training bundle.");
311
+ }
312
+ const evaluation = await trainingEvaluationSourceRef(evaluationSource);
313
+ const payload = {
314
+ schemaVersion: "openpond.managedRlPortableSubmission.v1",
315
+ sourceRunRef: `openpond:model-run:${manifest.id}`,
316
+ name: `OpenPond Managed \xB7 ${project.id}`.slice(0, 191),
317
+ idempotencyKey: input.idempotencyKey ?? `openpond-managed:${manifestHash}`.slice(0, 191),
318
+ modelProject: { id: project.hosted.projectId, portableProjectId: project.hosted.portableProjectId },
319
+ manifest,
320
+ sourceTaskset: taskset,
321
+ modelImprovementQualification: input.modelImprovementQualification ?? null,
322
+ recipe,
323
+ resolvedBundle: { manifest: bundle, files },
324
+ validationTasks: evaluationSource.tasks,
325
+ validationAssets: evaluationSource.assets
326
+ };
327
+ const artifactContent = {
328
+ schemaVersion: "openpond.trainingInputArtifactUpload.v2",
329
+ kind: "portable_training_bundle",
330
+ idempotencyKey: `stage:${input.idempotencyKey ?? manifestHash}`,
331
+ sourceManifest: { id: manifest.id, contentHash: manifestHash },
332
+ payload
333
+ };
334
+ const artifact = await parseAndVerifyTrainingInputArtifactUpload({ ...artifactContent, contentHash: await trainingInputArtifactUploadHash(artifactContent) });
335
+ const jobContent = {
336
+ schemaVersion: "openpond.trainingJobSubmission.v2",
337
+ idempotencyKey: input.idempotencyKey ?? `openpond-training-v2:${manifestHash}`,
338
+ name: `OpenPond Managed \xB7 ${project.id}`.slice(0, 200),
339
+ source: {
340
+ modelProject: { id: project.hosted.projectId, portableProjectId: project.hosted.portableProjectId, revision: project.revision, contentHash: project.hosted.etag },
341
+ harnessRunManifest: artifactContent.sourceManifest,
342
+ harnessRelease: manifest.harnessRelease,
343
+ taskset,
344
+ tasksetRelease: { id: taskset.id, contentHash: taskset.contentHash },
345
+ dataset: bundle.datasetRelease,
346
+ evaluation,
347
+ evidenceSets: bundle.evidenceSetRelease ? [bundle.evidenceSetRelease] : []
348
+ },
349
+ job: { kind: "policy_optimize", baseModel, recipe, rewardSource: input.rewardSource, resumeFrom: input.resumeFrom },
350
+ requestedCapabilities: [
351
+ { id: "managed_rl.policy.grpo", version: "1", required: true },
352
+ { id: `managed_rl.rollouts.${manifest.runtimeTarget.placement}`, version: "1", required: true },
353
+ ...project.trainingSetup.managedGpuRequirement === "h100_hbm3" ? [{ id: "managed_rl.gpu.h100_hbm3", version: "1", required: true }] : []
354
+ ],
355
+ placementObjective: project.trainingSetup.managedGpuPlacementObjective,
356
+ budget: { maximumSpendUsd: approval.maximumSpendUsd, maximumWallSeconds: Math.ceil(limits.wallTimeMs / 1e3) },
357
+ approval: { ...approval, approvedAt: manifest.approval.approvedAt, exportApproved: true }
358
+ };
359
+ const submission = await parseAndVerifyTrainingJobSubmission({ ...jobContent, contentHash: await trainingJobSubmissionHash(jobContent) });
360
+ return { artifact, submission };
361
+ }
362
+ function verifyFiles(bundle, files) {
363
+ if (files.length !== bundle.files.length || new Set(files.map((file) => file.path)).size !== files.length) throw new Error("The managed bundle file inventory changed.");
364
+ const indexed = new Map(files.map((file) => [file.path, file]));
365
+ return bundle.files.map((expected) => {
366
+ const file = indexed.get(expected.path);
367
+ if (!file || file.path.includes("\\") || file.path.includes("\0") || file.path.split("/").some((part) => !part || part === "." || part === "..") || file.path === "bundle-manifest.json" || file.encoding !== "base64") throw new Error("The managed bundle contains an invalid file.");
368
+ const bytes2 = Buffer.from(file.content, "base64");
369
+ if (bytes2.toString("base64") !== file.content || file.sha256 !== expected.sha256 || file.sizeBytes !== expected.sizeBytes || bytes2.byteLength !== expected.sizeBytes || sha256(bytes2) !== expected.sha256) throw new Error(`Managed training bundle file ${file.path} changed before upload.`);
370
+ return file;
371
+ });
372
+ }
373
+
374
+ // src/training-bundle.ts
375
+ function buildTasksetTrainingBundle(input) {
376
+ const { taskset, modelProject, harnessRelease, tasksetRelease } = input;
377
+ const setup = modelProject.trainingSetup;
378
+ const releasedHarness = ImmutableReleaseRefSchema.parse(harnessRelease);
379
+ const releasedTaskset = ImmutableReleaseRefSchema.parse(tasksetRelease);
380
+ void input.openpondRelease;
381
+ void input.workerProtocol;
382
+ const actualTasksetHash = computeTasksetHash(taskset);
383
+ if (actualTasksetHash !== taskset.contentHash) {
384
+ throw new Error(
385
+ "Taskset authoring state changed after its release was selected."
386
+ );
387
+ }
388
+ if (!setup.baseModel?.revision || !setup.baseModel.tokenizerRevision || !setup.baseModel.chatTemplateHash || !setup.recipe || !setup.tasksetRef || setup.tasksetRef.id !== taskset.id || setup.tasksetRef.contentHash !== taskset.contentHash) {
389
+ throw new Error(
390
+ "Model Run must bind an exact Taskset, Model revision, tokenizer, chat template, and Recipe."
391
+ );
392
+ }
393
+ const profileRelease = taskset.profileRelease ?? {
394
+ id: `profile_${taskset.profileId}`,
395
+ revision: taskset.revision,
396
+ contentHash: contentHash({
397
+ profileId: taskset.profileId,
398
+ sourceCommit: taskset.authoringProvenance.sourceCommit,
399
+ skillHash: taskset.authoringProvenance.skillHash
400
+ })
401
+ };
402
+ const assets = /* @__PURE__ */ new Map();
403
+ if (input.evaluationSource) {
404
+ const evaluation = TrainingEvaluationSourceSchema.parse(input.evaluationSource);
405
+ if (contentHash(evaluation.taskset) !== contentHash(setup.evaluationTasksetRef)) {
406
+ throw new Error("The evaluation source differs from the prepared Model configuration.");
407
+ }
408
+ assertTrainingEvaluationIsolation(taskset.tasks.filter((task) => task.split === "train"), evaluation.tasks);
409
+ assets.set(TRAINING_EVALUATION_SOURCE_PATH, new TextEncoder().encode(canonicalJson(evaluation)));
410
+ } else if (setup.evaluationTasksetRef) {
411
+ throw new Error("The prepared Model requires its pinned evaluation source bytes.");
412
+ }
413
+ const harnessSource = input.harnessSource ? validateHarnessSourcePackage(input.harnessSource, releasedHarness) : null;
414
+ if (setup.harnessRelease && !harnessSource) {
415
+ throw new Error("A selected Harness requires its complete immutable source package.");
416
+ }
417
+ if (harnessSource && (!setup.harnessRelease || setup.harnessRelease.id !== releasedHarness.id || setup.harnessRelease.contentHash !== releasedHarness.contentHash)) {
418
+ throw new Error("Harness source differs from the Model's explicit selection.");
419
+ }
420
+ if (harnessSource) {
421
+ const leavesLocalHost = input.runtime.placement !== "local" || input.compute.kind !== "local";
422
+ if (leavesLocalHost && (!harnessSource.agentSnapshot.portability.portable || harnessSource.harnessRelease.files.some((file) => file.visibility === "host_private"))) {
423
+ throw new Error("The selected Harness contains source that cannot leave its local host.");
424
+ }
425
+ addJsonAsset(assets, "harness/source-package.json", harnessSource);
426
+ }
427
+ addJsonAsset(assets, "harness/execution.json", HarnessSourceSelectionSchema.parse({
428
+ schemaVersion: "openpond.harnessSourceSelection.v1",
429
+ mode: harnessSource ? "selected_release" : "taskset_owned",
430
+ harnessRelease: releasedHarness,
431
+ sourcePackageHash: harnessSource?.contentHash ?? null
432
+ }));
433
+ const rewardExecution = resolvePortableTasksetRewardExecution(taskset, input.rewardExecution);
434
+ if (rewardExecution) {
435
+ const verifierAssets = input.verifierAssets ?? [];
436
+ const references = compileBoundGraders(rewardExecution.binding, rewardExecution.rewards).flatMap((grader) => grader.kind === "custom_verifier" ? [grader.verifierRef] : []);
437
+ const expected = new Set(references.map((reference) => reference.id));
438
+ if (new Set(verifierAssets.map((asset) => asset.id)).size !== verifierAssets.length || verifierAssets.some((asset) => !expected.has(asset.id))) throw new Error("Unexpected or duplicate private verifier asset.");
439
+ for (const reference of references) {
440
+ const asset = verifierAssets.find((asset2) => asset2.id === reference.id);
441
+ if (!asset || reference.visibility !== "verifier") throw new Error("Training bundle requires its private verifier asset.");
442
+ verifyLearningTextAsset2(asset, reference);
443
+ }
444
+ addJsonAsset(assets, "reward-binding.json", { kind: "reward_binding_v1", binding: rewardExecution.binding, rewards: rewardExecution.rewards, assets: verifierAssets });
445
+ } else if (input.verifierAssets?.length) {
446
+ throw new Error("Private verifier assets require a published Reward binding.");
447
+ }
448
+ addJsonAsset(assets, "environment.json", {
449
+ schemaVersion: "openpond.harnessEnvironment.v1",
450
+ environment: taskset.environment,
451
+ capabilities: taskset.capabilities,
452
+ policy: taskset.policy,
453
+ outputContract: taskset.metadata.tasksetOutputContract?.mode === "text" ? null : taskset.metadata.tasksetOutputContract ?? null
454
+ });
455
+ addJsonAsset(assets, "graders.json", {
456
+ schemaVersion: "openpond.harnessGraders.v1",
457
+ graders: taskset.graders,
458
+ fixtures: taskset.graderFixtures
459
+ });
460
+ addJsonAsset(assets, "tool-contract.json", {
461
+ schemaVersion: "openpond.harnessToolContract.v1",
462
+ toolNames: taskset.environment.toolNames,
463
+ actionBindings: taskset.environment.actionBindings ?? [],
464
+ capabilities: taskset.capabilities,
465
+ connectedAppScopes: taskset.policy.connectedAppScopes
466
+ });
467
+ const trainTasks = taskset.tasks.filter((task) => task.split === "train");
468
+ if (trainTasks.length > 0) {
469
+ addJsonAsset(assets, "dataset/train.json", {
470
+ schemaVersion: "openpond.datasetSplit.v1",
471
+ split: "train",
472
+ tasks: trainTasks
473
+ });
474
+ addTasksetAssets({
475
+ assets,
476
+ tasks: trainTasks,
477
+ tasksetAssetBytes: input.tasksetAssetBytes ?? /* @__PURE__ */ new Map()
478
+ });
479
+ } else if (taskset.datasetArtifact) {
480
+ addJsonAsset(assets, "dataset/artifact.json", taskset.datasetArtifact);
481
+ } else {
482
+ throw new Error("Taskset has no training dataset.");
483
+ }
484
+ const approvedSignals = Object.values(taskset.learningSignals).flat().filter((signal) => signal.approved);
485
+ for (const signal of approvedSignals) {
486
+ const value = bytes(signal);
487
+ assets.set(`evidence/signals/${sha256(value)}.json`, value);
488
+ }
489
+ const datasetRelease = {
490
+ id: `dataset_${taskset.id}_r${taskset.revision}`,
491
+ contentHash: contentHash({
492
+ taskset: releasedTaskset,
493
+ sourceRefs: taskset.sourceRefs,
494
+ datasetArtifact: taskset.datasetArtifact ?? null,
495
+ trainTasks
496
+ })
497
+ };
498
+ const evidenceSetRelease = approvedSignals.length > 0 ? {
499
+ id: `evidence_${taskset.id}_r${taskset.revision}`,
500
+ contentHash: contentHash({
501
+ taskset: releasedTaskset,
502
+ harnessRelease: releasedHarness,
503
+ datasetRelease,
504
+ profileRelease,
505
+ signals: approvedSignals
506
+ })
507
+ } : null;
508
+ const bundleContent = ResolvedTrainingBundleContentSchema.parse({
509
+ schemaVersion: "openpond.resolvedTrainingBundle.v1",
510
+ projection: "trainer",
511
+ harnessRelease: releasedHarness,
512
+ datasetRelease,
513
+ evidenceSetRelease,
514
+ files: [...assets.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([assetPath, value]) => ({
515
+ path: assetPath,
516
+ sha256: sha256(value),
517
+ sizeBytes: value.byteLength
518
+ }))
519
+ });
520
+ const resolvedBundleManifest = ResolvedTrainingBundleManifestSchema.parse({
521
+ ...bundleContent,
522
+ contentHash: contentHash(bundleContent)
523
+ });
524
+ const manifestContent = HarnessRunManifestContentSchema.parse({
525
+ schemaVersion: "openpond.harnessRunManifest.v1",
526
+ id: `manifest_${input.modelRunId}_${input.approval.approvalHash.slice(0, 16)}`,
527
+ harnessRelease,
528
+ datasetRelease,
529
+ evidenceSets: evidenceSetRelease ? [evidenceSetRelease] : [],
530
+ model: {
531
+ source: setup.baseModel.modelId,
532
+ revision: setup.baseModel.revision,
533
+ artifactHash: null,
534
+ tokenizerRevision: setup.baseModel.tokenizerRevision,
535
+ chatTemplateHash: setup.baseModel.chatTemplateHash
536
+ },
537
+ recipe: {
538
+ method: setup.recipe.method,
539
+ version: "openpond.trainingRecipe.v1",
540
+ configHash: contentHash(setup.recipe)
541
+ },
542
+ runtimeTarget: input.runtime,
543
+ computeTarget: input.compute,
544
+ engine: input.engine,
545
+ resolvedBundleHash: resolvedBundleManifest.contentHash,
546
+ secretLeaseRefs: input.secretLeaseRefs ?? [],
547
+ approval: input.approval,
548
+ createdAt: modelProject.updatedAt
549
+ });
550
+ const manifest = HarnessRunManifestSchema.parse({
551
+ ...manifestContent,
552
+ contentHash: contentHash(manifestContent)
553
+ });
554
+ return {
555
+ manifest,
556
+ resolvedBundleManifest,
557
+ assets,
558
+ profileRelease,
559
+ harnessRelease: releasedHarness,
560
+ tasksetRelease: releasedTaskset,
561
+ datasetRelease,
562
+ evidenceSetRelease
563
+ };
564
+ }
565
+ function addTasksetAssets(input) {
566
+ const expected = /* @__PURE__ */ new Set();
567
+ for (const task of input.tasks) {
568
+ for (const asset of task.assets ?? []) {
569
+ if (expected.has(asset.artifactRef)) {
570
+ throw new Error(
571
+ `Training asset path ${asset.artifactRef} is shared by multiple tasks.`
572
+ );
573
+ }
574
+ expected.add(asset.artifactRef);
575
+ const value = input.tasksetAssetBytes.get(asset.artifactRef);
576
+ if (!value) {
577
+ throw new Error(
578
+ `Resolved Training Bundle is missing Work asset ${asset.artifactRef}.`
579
+ );
580
+ }
581
+ if (value.byteLength !== asset.sizeBytes || sha256(value) !== asset.sha256) {
582
+ throw new Error(
583
+ `Resolved Training Bundle Work asset ${asset.artifactRef} failed immutable verification.`
584
+ );
585
+ }
586
+ input.assets.set(asset.artifactRef, value);
587
+ }
588
+ }
589
+ for (const assetPath of input.tasksetAssetBytes.keys()) {
590
+ if (!expected.has(assetPath)) {
591
+ throw new Error(
592
+ `Resolved Training Bundle received unexpected Work asset ${assetPath}.`
593
+ );
594
+ }
595
+ }
596
+ }
597
+ function addJsonAsset(assets, assetPath, value) {
598
+ assets.set(assetPath, bytes(value));
599
+ }
600
+ function bytes(value) {
601
+ return Buffer.from(canonicalJson(value), "utf8");
602
+ }
603
+ export {
604
+ ComputeTargetBindingSchema,
605
+ HarnessRunManifestContentSchema,
606
+ HarnessRunManifestSchema,
607
+ HarnessRuntimeTargetBindingSchema,
608
+ OpaqueSecretLeaseRefSchema,
609
+ ResolvedTrainingBundleContentSchema,
610
+ ResolvedTrainingBundleManifestSchema,
611
+ ScopedSecretDeclarationSchema,
612
+ TrainingEngineBindingSchema,
613
+ buildTasksetTrainingBundle,
614
+ materializeLearningBatchTaskset,
615
+ prepareManagedTrainingSubmission,
616
+ prepareReviewedLearningBatch,
617
+ scanAndRedactEvidence
618
+ };
619
+ //# sourceMappingURL=training-bundle.js.map