@openpond/evals 0.12.5 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LEARNING.md +5 -0
- package/dist/javascript-isolate-process-source.js +1 -1
- package/dist/javascript-verifier-worker-source.js +1 -1
- package/dist/learning/admission.js +1 -1
- package/dist/learning/contracts.js +12 -0
- package/dist/learning/iteration-dispatch-contracts.js +4 -0
- package/dist/learning/iteration-dispatch-worker.js +4 -0
- package/dist/learning/iteration-eligibility.js +2 -1
- package/dist/learning/iteration-exposure.js +30 -0
- package/dist/learning/iteration-reservation-cancellation.js +12 -3
- package/dist/learning/iteration-reservation-contracts.js +5 -3
- package/dist/learning/iteration-reservation-service.js +10 -2
- package/dist/learning/iteration-training-parent.js +3 -1
- package/dist/learning/reward-authoring.js +4 -2
- package/dist/learning/service.js +2 -0
- package/dist/sql-execution-process-source.js +1 -1
- package/dist/task-schema-validation.js +10 -2
- package/dist/types/learning/batch-service.d.ts +1 -0
- package/dist/types/learning/batch-service.d.ts.map +1 -1
- package/dist/types/learning/contracts.d.ts +42 -0
- package/dist/types/learning/contracts.d.ts.map +1 -1
- package/dist/types/learning/iteration-dispatch-contracts.d.ts +13 -0
- package/dist/types/learning/iteration-dispatch-contracts.d.ts.map +1 -1
- package/dist/types/learning/iteration-dispatch-worker.d.ts.map +1 -1
- package/dist/types/learning/iteration-eligibility.d.ts.map +1 -1
- package/dist/types/learning/iteration-exposure.d.ts +13 -0
- package/dist/types/learning/iteration-exposure.d.ts.map +1 -0
- package/dist/types/learning/iteration-reservation-cancellation.d.ts.map +1 -1
- package/dist/types/learning/iteration-reservation-contracts.d.ts +13 -3
- package/dist/types/learning/iteration-reservation-contracts.d.ts.map +1 -1
- package/dist/types/learning/iteration-reservation-service.d.ts.map +1 -1
- package/dist/types/learning/iteration-training-parent.d.ts.map +1 -1
- package/dist/types/learning/operations.d.ts +38 -0
- package/dist/types/learning/operations.d.ts.map +1 -1
- package/dist/types/learning/repository.d.ts +28 -2
- package/dist/types/learning/repository.d.ts.map +1 -1
- package/dist/types/learning/reward-authoring.d.ts.map +1 -1
- package/dist/types/learning/service.d.ts.map +1 -1
- package/dist/types/learning/transport.d.ts +1009 -970
- package/dist/types/learning/transport.d.ts.map +1 -1
- package/dist/types/task-schema-validation.d.ts.map +1 -1
- package/package.json +2 -2
- package/schemas/learning/v1/command-request.schema.json +119 -93
- package/schemas/learning/v1/consumption.schema.json +40 -4
- package/schemas/learning/v1/evidence.schema.json +3 -0
- package/schemas/learning/v1/example-submission.schema.json +3 -0
- package/schemas/learning/v1/source.schema.json +20 -0
|
@@ -88,7 +88,7 @@ export function taskRecordFromEvidence(evidence, definition) {
|
|
|
88
88
|
return TaskRecordSchema.parse({
|
|
89
89
|
id: evidence.id, clusterKey: evidence.submission.familyKey, split: evidence.submission.split,
|
|
90
90
|
input: evidence.submission.input, expectedOutput: evidence.submission.expected,
|
|
91
|
-
policyVisibleContext: { instructions: definition.instructions },
|
|
91
|
+
policyVisibleContext: evidence.submission.policyVisibleContext ?? { instructions: definition.instructions },
|
|
92
92
|
privilegedContextRef: evidence.submission.evaluatorContext === null ? null : `task-evidence:${evidence.contentHash}`,
|
|
93
93
|
artifactRefs: evidence.submission.assets, tags: [definition.category],
|
|
94
94
|
...(requiredOutputs === undefined ? {} : { requiredOutputs }),
|
|
@@ -89,6 +89,13 @@ export const LearningReviewOriginSchema = z
|
|
|
89
89
|
rewardBinding: LearningRevisionRefSchema,
|
|
90
90
|
})
|
|
91
91
|
.strict();
|
|
92
|
+
/** A host must verify this retained import receipt before resolving package
|
|
93
|
+
* bytes. This portable reference is neither an approval nor an access grant. */
|
|
94
|
+
export const LearningDatasetOriginSchema = z.object({
|
|
95
|
+
receiptId: ReleaseIdSchema,
|
|
96
|
+
packageHash: ReleaseHashSchema,
|
|
97
|
+
taskset: LearningRevisionRefSchema,
|
|
98
|
+
}).strict();
|
|
92
99
|
export const LearningSourceContentSchema = z
|
|
93
100
|
.object({
|
|
94
101
|
schemaVersion: z.literal("openpond.learningSource.v1"),
|
|
@@ -102,6 +109,7 @@ export const LearningSourceContentSchema = z
|
|
|
102
109
|
mapping: TaskSourceMappingSchema.nullable(),
|
|
103
110
|
adapterVersion: z.string().trim().min(1).max(200).nullable(),
|
|
104
111
|
reviewOrigin: LearningReviewOriginSchema.optional(),
|
|
112
|
+
datasetOrigin: LearningDatasetOriginSchema.optional(),
|
|
105
113
|
/** Host-retained authorization for materializing neutral conversation
|
|
106
114
|
* evidence. The receipt is reauthorized at preparation and paid admission;
|
|
107
115
|
* this portable reference alone never grants access or training consent. */
|
|
@@ -115,6 +123,8 @@ export const LearningSourceContentSchema = z
|
|
|
115
123
|
})
|
|
116
124
|
.strict()
|
|
117
125
|
.superRefine((source, context) => {
|
|
126
|
+
if (source.datasetOrigin && (source.reviewOrigin || source.capturedOrigin))
|
|
127
|
+
context.addIssue({ code: "custom", path: ["datasetOrigin"], message: "Choose one retained source origin." });
|
|
118
128
|
if (source.kind !== "direct" && !source.mapping)
|
|
119
129
|
context.addIssue({
|
|
120
130
|
code: "custom",
|
|
@@ -149,6 +159,8 @@ export const TaskExampleSubmissionSchema = z
|
|
|
149
159
|
familyKey: ReleaseIdSchema.nullable(),
|
|
150
160
|
split: TaskSplitSchema,
|
|
151
161
|
input: LearningJsonObjectSchema,
|
|
162
|
+
/** Per-example policy context remains separate from evaluator-only data. */
|
|
163
|
+
policyVisibleContext: LearningJsonObjectSchema.optional(),
|
|
152
164
|
observedOutput: LearningJsonObjectSchema.nullable(),
|
|
153
165
|
expected: LearningJsonObjectSchema.nullable(),
|
|
154
166
|
evaluatorContext: LearningJsonObjectSchema.nullable(),
|
|
@@ -89,7 +89,11 @@ export const LearningIterationObservationSchema = z.object({
|
|
|
89
89
|
evaluation: ImmutableReleaseRefSchema.nullable(),
|
|
90
90
|
candidate: ImmutableReleaseRefSchema.nullable(),
|
|
91
91
|
failure: z.string().max(20_000).nullable(),
|
|
92
|
+
coverage: z.array(z.object({ evidence: LearningRevisionRefSchema,
|
|
93
|
+
outcome: z.enum(["trained", "exposed", "unexposed", "unknown"]) }).strict()).max(10_000).optional(),
|
|
92
94
|
}).strict().superRefine((value, context) => {
|
|
95
|
+
if (value.coverage && (!["succeeded", "failed", "cancelled"].includes(value.status) || !value.cleanupComplete || !value.receipt))
|
|
96
|
+
context.addIssue({ code: "custom", path: ["coverage"], message: "Coverage requires authoritative terminal cleanup evidence." });
|
|
93
97
|
if (["succeeded", "failed", "cancelled"].includes(value.status) && (!value.cleanupComplete || !value.receipt)) {
|
|
94
98
|
context.addIssue({ code: "custom", path: ["receipt"], message: "Terminal execution requires an authoritative cleanup receipt." });
|
|
95
99
|
}
|
|
@@ -6,6 +6,7 @@ import { LearningIterationAdmissionSchema, LearningIterationDispatchSchema, Lear
|
|
|
6
6
|
import { requireLearningRelease, requireLearningResource } from "./repository.js";
|
|
7
7
|
import { deferLearningIterationSubmission } from "./iteration-dispatch-deferral.js";
|
|
8
8
|
import { LearningIterationSubmissionDeferralSchema } from "./iteration-dispatch-contracts.js";
|
|
9
|
+
import { settleLearningIterationExposure } from "./iteration-exposure.js";
|
|
9
10
|
const closed = new Set(["accepted", "rejected", "failed", "cancelled", "completed_without_candidate"]);
|
|
10
11
|
export function createLearningIterationWorker(repository, executor, options) {
|
|
11
12
|
const now = options.now ?? (() => new Date().toISOString());
|
|
@@ -85,6 +86,9 @@ export function createLearningIterationWorker(repository, executor, options) {
|
|
|
85
86
|
terminalReceipt: outcome?.receipt ?? null, leaseOwner: null, leaseExpiresAt: null, admission: null,
|
|
86
87
|
nextAttemptAt: terminal ? null : new Date(Date.parse(now()) + 5_000).toISOString(), consecutiveFailures: 0, lastError: null });
|
|
87
88
|
if (terminal) {
|
|
89
|
+
await settleLearningIterationExposure(tx, { chainId: current.chainId, iterationId: iteration.id,
|
|
90
|
+
batch: await requireLearningRelease(tx, "batch", current.batch), outcome,
|
|
91
|
+
unstarted: !outcome && !current.submissionStartedAt });
|
|
88
92
|
const reservation = await requireLearningResource(tx, "reservation", iteration.id);
|
|
89
93
|
const settled = LearningIterationReservationSchema.parse({ ...reservation, revision: reservation.revision + 1,
|
|
90
94
|
budget: { ...reservation.budget, reservedSpendUsd: 0, settledSpendUsd: updated.spendUsd, settledAt: now() }, updatedAt: now() });
|
|
@@ -46,7 +46,8 @@ export async function inspectIterationEligibility(transaction, policy, options =
|
|
|
46
46
|
counts.excluded += 1;
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
const consumption = await transaction.get("consumption", learningConsumptionId(chainId, evidence));
|
|
50
|
+
if (consumption && consumption.exposure !== "unexposed") {
|
|
50
51
|
counts.consumed += 1;
|
|
51
52
|
continue;
|
|
52
53
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { contentHash } from "@openpond/harness";
|
|
2
|
+
import { sameLearningRef } from "./contracts.js";
|
|
3
|
+
import { LearningDomainError } from "./errors.js";
|
|
4
|
+
import { LearningConsumptionSchema } from "./iteration-reservation-contracts.js";
|
|
5
|
+
/** Called only in fenced terminal settlement. Absence/timeout is never evidence
|
|
6
|
+
* of zero exposure; unresolved rows keep their original reservation. */
|
|
7
|
+
export async function settleLearningIterationExposure(tx, input) {
|
|
8
|
+
const coverage = input.outcome?.coverage;
|
|
9
|
+
if (!input.unstarted && !coverage)
|
|
10
|
+
return;
|
|
11
|
+
if (coverage && (coverage.length !== input.batch.examples.length ||
|
|
12
|
+
new Set(coverage.map(entry => contentHash(entry.evidence))).size !== coverage.length ||
|
|
13
|
+
coverage.some(entry => !input.batch.examples.some(example => sameLearningRef(example.evidence, entry.evidence)))))
|
|
14
|
+
throw new LearningDomainError("learning_execution_coverage_mismatch", 409);
|
|
15
|
+
for (const example of input.batch.examples) {
|
|
16
|
+
const id = `consumption-${contentHash([input.chainId, example.evidence.id, example.evidence.revision])}`;
|
|
17
|
+
const current = await tx.get("consumption", id);
|
|
18
|
+
if (!current || current.iterationId !== input.iterationId || !sameLearningRef(current.evidence, example.evidence))
|
|
19
|
+
throw new LearningDomainError("learning_execution_consumption_mismatch", 409);
|
|
20
|
+
const exposure = input.unstarted ? "unexposed" : coverage.find(entry => sameLearningRef(entry.evidence, example.evidence)).outcome;
|
|
21
|
+
if (exposure === "unknown")
|
|
22
|
+
continue;
|
|
23
|
+
// A terminal replay cannot downgrade already proven numerical exposure.
|
|
24
|
+
if (["trained", "exposed"].includes(current.exposure ?? "") && exposure !== current.exposure)
|
|
25
|
+
throw new LearningDomainError("learning_execution_coverage_conflict", 409);
|
|
26
|
+
const next = LearningConsumptionSchema.parse({ ...current, revision: current.revision + 1,
|
|
27
|
+
exposure, executionReceipt: input.outcome?.receipt ?? null });
|
|
28
|
+
await tx.put("consumption", next, current.revision, { parentId: input.chainId });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { LearningIterationSchema } from "./contracts.js";
|
|
2
2
|
import { LearningDomainError } from "./errors.js";
|
|
3
3
|
import { LearningChainSchema, LearningIterationReservationSchema } from "./iteration-reservation-contracts.js";
|
|
4
|
-
import { LearningConflictError, requireLearningResource } from "./repository.js";
|
|
4
|
+
import { LearningConflictError, requireLearningRelease, requireLearningResource } from "./repository.js";
|
|
5
|
+
import { settleLearningIterationExposure } from "./iteration-exposure.js";
|
|
5
6
|
/** Only a reservation that has never been dispatched can release spend locally.
|
|
6
7
|
* Submitted executions require authoritative cancellation and settlement. */
|
|
7
8
|
export async function cancelLearningIterationReservation(transaction, input, now) {
|
|
@@ -14,6 +15,9 @@ export async function cancelLearningIterationReservation(transaction, input, now
|
|
|
14
15
|
if (iteration.trainingJob || iteration.evaluationJob || !["ready", "waiting_for_data", "waiting_for_review"].includes(iteration.status)) {
|
|
15
16
|
throw new LearningDomainError("learning_execution_cancellation_required", 409);
|
|
16
17
|
}
|
|
18
|
+
const dispatch = await transaction.get("dispatch", iteration.dispatchId);
|
|
19
|
+
if (dispatch?.submissionStartedAt || dispatch?.execution || dispatch?.leaseOwner)
|
|
20
|
+
throw new LearningDomainError("learning_execution_cancellation_required", 409);
|
|
17
21
|
const updated = LearningIterationSchema.parse({ ...iteration, revision: iteration.revision + 1, status: "cancelled", updatedAt: now });
|
|
18
22
|
const released = LearningIterationReservationSchema.parse({
|
|
19
23
|
...reservation, revision: reservation.revision + 1,
|
|
@@ -25,7 +29,12 @@ export async function cancelLearningIterationReservation(transaction, input, now
|
|
|
25
29
|
if (chain.activeIterationId === iteration.id) {
|
|
26
30
|
await transaction.put("chain", LearningChainSchema.parse({ ...chain, revision: chain.revision + 1, activeIterationId: null, updatedAt: now }), chain.revision, { parentId: chain.modelProjectId });
|
|
27
31
|
}
|
|
28
|
-
//
|
|
29
|
-
//
|
|
32
|
+
// Retain the reservation history; only this transaction's fenced proof that
|
|
33
|
+
// dispatch never started makes its exact evidence eligible again.
|
|
34
|
+
if (iteration.batch)
|
|
35
|
+
await settleLearningIterationExposure(transaction, {
|
|
36
|
+
chainId: reservation.chainId, iterationId: iteration.id,
|
|
37
|
+
batch: await requireLearningRelease(transaction, "batch", iteration.batch), outcome: null, unstarted: true,
|
|
38
|
+
});
|
|
30
39
|
return [{ kind: "iteration", id: updated.id, revision: updated.revision }, { kind: "reservation", id: released.id, revision: released.revision }];
|
|
31
40
|
}
|
|
@@ -24,17 +24,19 @@ export const LearningEligibilityCountsSchema = z.object({
|
|
|
24
24
|
excluded: z.number().int().nonnegative(),
|
|
25
25
|
consumed: z.number().int().nonnegative(),
|
|
26
26
|
}).strict();
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
27
|
+
/** Reservation history is retained; authoritative terminal exposure alone may
|
|
28
|
+
* release proven-unexposed evidence. Ambiguous execution remains reserved. */
|
|
29
29
|
export const LearningConsumptionSchema = z.object({
|
|
30
30
|
schemaVersion: z.literal("openpond.learningConsumption.v1"),
|
|
31
31
|
id: ReleaseIdSchema,
|
|
32
|
-
revision: z.
|
|
32
|
+
revision: z.number().int().positive(),
|
|
33
33
|
chainId: ReleaseIdSchema,
|
|
34
34
|
iterationId: ReleaseIdSchema,
|
|
35
35
|
evidence: LearningRevisionRefSchema,
|
|
36
36
|
decision: LearningRevisionRefSchema,
|
|
37
37
|
reservedAt: ReleaseTimestampSchema,
|
|
38
|
+
exposure: z.enum(["reserved", "trained", "exposed", "unexposed"]).optional(),
|
|
39
|
+
executionReceipt: z.object({ id: ReleaseIdSchema, contentHash: ReleaseHashSchema }).strict().nullable().optional(),
|
|
38
40
|
}).strict();
|
|
39
41
|
/** Dispatch must reconcile this reservation, never reserve a second batch on retry. */
|
|
40
42
|
export const LearningIterationReservationSchema = z.object({
|
|
@@ -24,6 +24,8 @@ export async function reserveLearningIteration(transaction, input, actorId, now)
|
|
|
24
24
|
return [{ kind: "iteration", id, revision: (await requireLearningResource(transaction, "iteration", id)).revision }, { kind: "reservation", id, revision: previous.revision }];
|
|
25
25
|
}
|
|
26
26
|
const current = await requireLearningResource(transaction, "policy", policy.id);
|
|
27
|
+
if (policy.training.replayBatches.length)
|
|
28
|
+
throw new LearningDomainError("learning_replay_not_supported", 422);
|
|
27
29
|
if (!sameLearningRef(learningRef(current), input.policy))
|
|
28
30
|
throw new LearningDomainError("learning_policy_revision_stale", 409);
|
|
29
31
|
if (!policy.enabled)
|
|
@@ -58,6 +60,7 @@ export async function reserveLearningIteration(transaction, input, actorId, now)
|
|
|
58
60
|
const parent = await resolveLearningTrainingParent(transaction, policy, acceptedParent);
|
|
59
61
|
let batch = null;
|
|
60
62
|
if (ready) {
|
|
63
|
+
await transaction.validateTrainingReservation?.({ policy, parent: parent.trainingParentSelection });
|
|
61
64
|
await assertAvailableBudget(transaction, chainId, now, policy.limits.maxIterationSpendUsd, policy.limits.maxDailySpendUsd);
|
|
62
65
|
const sealed = await sealLearningBatch(transaction, {
|
|
63
66
|
action: "seal_batch", operationId: input.operationId, batchId: `batch-${contentHash(id)}`,
|
|
@@ -68,11 +71,16 @@ export async function reserveLearningIteration(transaction, input, actorId, now)
|
|
|
68
71
|
pointers.push(...sealed);
|
|
69
72
|
batch = learningRef(await requireLearningResource(transaction, "batch", sealed[0].id));
|
|
70
73
|
for (const item of eligibility.selected) {
|
|
74
|
+
const consumptionId = learningConsumptionId(chainId, item.evidence);
|
|
75
|
+
const prior = await transaction.get("consumption", consumptionId);
|
|
76
|
+
if (prior && prior.exposure !== "unexposed")
|
|
77
|
+
throw new LearningDomainError("learning_evidence_already_reserved", 409);
|
|
71
78
|
const consumption = LearningConsumptionSchema.parse({
|
|
72
|
-
schemaVersion: "openpond.learningConsumption.v1", id:
|
|
79
|
+
schemaVersion: "openpond.learningConsumption.v1", id: consumptionId, revision: (prior?.revision ?? 0) + 1,
|
|
73
80
|
chainId, iterationId: id, evidence: learningRef(item.evidence), decision: learningRef(item.decision), reservedAt: now,
|
|
81
|
+
exposure: "reserved", executionReceipt: null,
|
|
74
82
|
});
|
|
75
|
-
await transaction.put("consumption", consumption, 0, { parentId: chainId });
|
|
83
|
+
await transaction.put("consumption", consumption, prior?.revision ?? 0, { parentId: chainId });
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
const iteration = LearningIterationSchema.parse({
|
|
@@ -29,7 +29,9 @@ export async function latestAcceptedLearningParent(tx, chainId, requireComplete
|
|
|
29
29
|
/** Reservation captures an accepted revision before dispatch or later reviews.
|
|
30
30
|
* Switching the underlying base requires a separately reviewed learning chain. */
|
|
31
31
|
export async function resolveLearningTrainingParent(tx, policy, selection) {
|
|
32
|
-
|
|
32
|
+
// Supervised iterations retrain the reviewed batch from the pinned base.
|
|
33
|
+
// Accepted SFT candidates remain review evidence, never a GRPO checkpoint handoff.
|
|
34
|
+
if (!selection || policy.training.method === "sft")
|
|
33
35
|
return { trainingParent: policy.trainingParent, trainingParentSelection: null };
|
|
34
36
|
const parent = await requireLearningResource(tx, "iteration", selection.iterationId, selection.iterationRevision);
|
|
35
37
|
const sourcePolicy = await requireLearningRelease(tx, "policy", parent.policy);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RewardReleaseSchema, createRewardRelease
|
|
1
|
+
import { RewardReleaseSchema, createRewardRelease } from "../rewards.js";
|
|
2
2
|
import { assertBoundedTaskJson } from "../task-schema.js";
|
|
3
3
|
import { RewardAuthoringFieldsSchema, RewardFixtureAuthoringFieldsSchema } from "./authoring.js";
|
|
4
4
|
import { createLearningTextAsset, verifyLearningTextAsset } from "./assets.js";
|
|
@@ -65,7 +65,9 @@ export function compileRewardAuthoring(input) {
|
|
|
65
65
|
const assets = [...(asset ? [asset] : []), ...(fixtureAsset ? [fixtureAsset] : [])];
|
|
66
66
|
const reward = createRewardRelease({
|
|
67
67
|
schemaVersion: "openpond.rewardRelease.v1", id: input.id, revision: (base?.revision ?? 0) + 1,
|
|
68
|
-
|
|
68
|
+
// Derived defaults belong to feedbackKeyForReward at read time. Adding an
|
|
69
|
+
// unauthored field here changes the immutable calibration snapshot.
|
|
70
|
+
name: fields.name, description: fields.description, ...(fields.feedbackKey === undefined ? {} : { feedbackKey: fields.feedbackKey }), implementation,
|
|
69
71
|
rawScore: fields.kind === "learned_model" ? { minimum: authoringNumber(fields.minimum, "Minimum score"), maximum: authoringNumber(fields.maximum, "Maximum score") } : { minimum: 0, maximum: 1 },
|
|
70
72
|
assets: assets.map(value => value.asset), ...(fixtureAsset ? { fixtureSetRef: fixtureAsset.asset } : {}),
|
|
71
73
|
...(calibrationCheckRef ? { calibrationCheckRef } : {}),
|
package/dist/learning/service.js
CHANGED
|
@@ -178,6 +178,8 @@ export function createLearningService(repository, options = {}) {
|
|
|
178
178
|
break;
|
|
179
179
|
}
|
|
180
180
|
case "policy": {
|
|
181
|
+
if (input.content.training.replayBatches.length)
|
|
182
|
+
throw new LearningDomainError("learning_replay_not_supported", 422, "Replay requires an executor that materializes exact examples and weights.");
|
|
181
183
|
const definition = await requireLearningRelease(transaction, "definition", input.content.taskDefinition);
|
|
182
184
|
if (!sameLearningRef(definition.rewardBinding, input.content.rewardBinding))
|
|
183
185
|
throw new LearningDomainError("learning_policy_binding_mismatch", 422);
|