@openpond/evals 0.11.15 → 0.11.17
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/CONTRACT.md +9 -0
- package/dist/connected-evidence/contracts.js +72 -0
- package/dist/connected-evidence/imports.js +71 -0
- package/dist/connected-evidence/index.js +5 -0
- package/dist/connected-evidence/normalize.js +97 -0
- package/dist/connected-evidence/python-correctness.js +73 -0
- package/dist/connected-evidence/sources/claude-code.js +71 -0
- package/dist/connected-evidence/sources/codex.js +52 -0
- package/dist/connected-evidence/sources/history.js +86 -0
- package/dist/connected-evidence/sources/usage.js +15 -0
- package/dist/connected-evidence/usage-summary.js +52 -0
- package/dist/experiments.js +41 -7
- package/dist/graders.js +9 -6
- package/dist/human-review/comparison.js +161 -0
- package/dist/human-review/contracts.js +110 -0
- package/dist/human-review/execution.js +61 -0
- package/dist/human-review/handoff.js +18 -0
- package/dist/human-review/index.js +9 -0
- package/dist/human-review/local-publication.js +18 -0
- package/dist/human-review/repository.js +1 -0
- package/dist/human-review/results.js +18 -0
- package/dist/human-review/service.js +268 -0
- package/dist/human-review/task-proposals.js +39 -0
- package/dist/index.js +1 -0
- package/dist/javascript-verifier-contract.js +2 -2
- package/dist/learning/authoring.js +2 -1
- package/dist/learning/contracts.js +353 -81
- package/dist/learning/index.js +1 -0
- package/dist/learning/reward-authoring.js +5 -2
- package/dist/learning/training-handoff.js +32 -0
- package/dist/metrics-node.js +2 -0
- package/dist/profile-evaluation-target.js +7 -0
- package/dist/profile-evaluations.js +7 -10
- package/dist/profile-external-evaluation.js +35 -0
- package/dist/profile-source-candidate.js +5 -0
- package/dist/rewards.js +5 -3
- package/dist/taskset-run-contract.js +10 -0
- package/dist/tasksets.js +2 -0
- package/dist/types/builtin-benchmarks/harness-refiner.d.ts +75 -1
- package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -1
- package/dist/types/conformance.d.ts +150 -2
- package/dist/types/conformance.d.ts.map +1 -1
- package/dist/types/connected-evidence/contracts.d.ts +317 -0
- package/dist/types/connected-evidence/contracts.d.ts.map +1 -0
- package/dist/types/connected-evidence/imports.d.ts +19 -0
- package/dist/types/connected-evidence/imports.d.ts.map +1 -0
- package/dist/types/connected-evidence/index.d.ts +6 -0
- package/dist/types/connected-evidence/index.d.ts.map +1 -0
- package/dist/types/connected-evidence/normalize.d.ts +25 -0
- package/dist/types/connected-evidence/normalize.d.ts.map +1 -0
- package/dist/types/connected-evidence/python-correctness.d.ts +196 -0
- package/dist/types/connected-evidence/python-correctness.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/claude-code.d.ts +74 -0
- package/dist/types/connected-evidence/sources/claude-code.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/codex.d.ts +74 -0
- package/dist/types/connected-evidence/sources/codex.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/history.d.ts +146 -0
- package/dist/types/connected-evidence/sources/history.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/usage.d.ts +3 -0
- package/dist/types/connected-evidence/sources/usage.d.ts.map +1 -0
- package/dist/types/connected-evidence/usage-summary.d.ts +162 -0
- package/dist/types/connected-evidence/usage-summary.d.ts.map +1 -0
- package/dist/types/evidence/conformance.d.ts +28 -28
- package/dist/types/evidence/contracts.d.ts +27 -27
- package/dist/types/execution-contracts.d.ts +159 -5
- package/dist/types/execution-contracts.d.ts.map +1 -1
- package/dist/types/experiments.d.ts +498 -7
- package/dist/types/experiments.d.ts.map +1 -1
- package/dist/types/graders.d.ts +21 -1
- package/dist/types/graders.d.ts.map +1 -1
- package/dist/types/human-review/comparison.d.ts +1959 -0
- package/dist/types/human-review/comparison.d.ts.map +1 -0
- package/dist/types/human-review/contracts.d.ts +2719 -0
- package/dist/types/human-review/contracts.d.ts.map +1 -0
- package/dist/types/human-review/execution.d.ts +298 -0
- package/dist/types/human-review/execution.d.ts.map +1 -0
- package/dist/types/human-review/handoff.d.ts +124 -0
- package/dist/types/human-review/handoff.d.ts.map +1 -0
- package/dist/types/human-review/index.d.ts +10 -0
- package/dist/types/human-review/index.d.ts.map +1 -0
- package/dist/types/human-review/local-publication.d.ts +165 -0
- package/dist/types/human-review/local-publication.d.ts.map +1 -0
- package/dist/types/human-review/repository.d.ts +43 -0
- package/dist/types/human-review/repository.d.ts.map +1 -0
- package/dist/types/human-review/results.d.ts +118 -0
- package/dist/types/human-review/results.d.ts.map +1 -0
- package/dist/types/human-review/service.d.ts +878 -0
- package/dist/types/human-review/service.d.ts.map +1 -0
- package/dist/types/human-review/task-proposals.d.ts +70 -0
- package/dist/types/human-review/task-proposals.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/javascript-environment-contract.d.ts +1 -1
- package/dist/types/javascript-verifier-contract.d.ts +1 -1
- package/dist/types/javascript-verifier-contract.d.ts.map +1 -1
- package/dist/types/learned-preference.d.ts +2 -2
- package/dist/types/learning/acceptance-group.d.ts +13 -13
- package/dist/types/learning/acceptance-plan.d.ts +1 -1
- package/dist/types/learning/admission.d.ts +78 -1
- package/dist/types/learning/admission.d.ts.map +1 -1
- package/dist/types/learning/authoring-service.d.ts +2 -0
- package/dist/types/learning/authoring-service.d.ts.map +1 -1
- package/dist/types/learning/authoring.d.ts +20 -0
- package/dist/types/learning/authoring.d.ts.map +1 -1
- package/dist/types/learning/batch-service.d.ts +76 -2
- package/dist/types/learning/batch-service.d.ts.map +1 -1
- package/dist/types/learning/contracts.d.ts +759 -24
- package/dist/types/learning/contracts.d.ts.map +1 -1
- package/dist/types/learning/index.d.ts +1 -0
- package/dist/types/learning/index.d.ts.map +1 -1
- package/dist/types/learning/iteration-candidate-decision.d.ts +2 -2
- package/dist/types/learning/iteration-dispatch-contracts.d.ts +2 -2
- package/dist/types/learning/operations.d.ts +1118 -8
- package/dist/types/learning/operations.d.ts.map +1 -1
- package/dist/types/learning/repository.d.ts +402 -15
- 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/reward-calibration.d.ts +74 -0
- package/dist/types/learning/reward-calibration.d.ts.map +1 -1
- package/dist/types/learning/reward-checks.d.ts +14 -2
- package/dist/types/learning/reward-checks.d.ts.map +1 -1
- package/dist/types/learning/training-handoff.d.ts +201 -0
- package/dist/types/learning/training-handoff.d.ts.map +1 -0
- package/dist/types/learning/transport.d.ts +963 -21
- package/dist/types/learning/transport.d.ts.map +1 -1
- package/dist/types/metrics-node.d.ts +3 -1
- package/dist/types/metrics-node.d.ts.map +1 -1
- package/dist/types/model-improvement-qualification.d.ts +3 -3
- package/dist/types/native-harness-experiment-attempt.d.ts +2 -2
- package/dist/types/profile-evaluation-comparisons.d.ts +230 -0
- package/dist/types/profile-evaluation-comparisons.d.ts.map +1 -1
- package/dist/types/profile-evaluation-target.d.ts +14 -0
- package/dist/types/profile-evaluation-target.d.ts.map +1 -0
- package/dist/types/profile-evaluations.d.ts +117 -14
- package/dist/types/profile-evaluations.d.ts.map +1 -1
- package/dist/types/profile-external-evaluation.d.ts +404 -0
- package/dist/types/profile-external-evaluation.d.ts.map +1 -0
- package/dist/types/profile-source-candidate.d.ts +15 -0
- package/dist/types/profile-source-candidate.d.ts.map +1 -0
- package/dist/types/review-conformance.d.ts +7 -7
- package/dist/types/rewards.d.ts +253 -0
- package/dist/types/rewards.d.ts.map +1 -1
- package/dist/types/rollouts.d.ts +3 -3
- package/dist/types/taskset-run-contract.d.ts +230 -0
- package/dist/types/taskset-run-contract.d.ts.map +1 -1
- package/dist/types/tasksets.d.ts +311 -3
- package/dist/types/tasksets.d.ts.map +1 -1
- package/dist/types/telemetry-analysis.d.ts +2 -2
- package/dist/types/telemetry-bundle.d.ts +6 -6
- package/dist/types/telemetry.d.ts +3 -3
- package/package.json +9 -1
- package/schemas/learning/v1/command-request.schema.json +1745 -409
- package/schemas/learning/v1/decision.schema.json +11 -0
- package/schemas/learning/v1/draft.schema.json +11 -0
- package/schemas/learning/v1/grade.schema.json +11 -0
- package/schemas/learning/v1/package.schema.json +532 -12
- package/schemas/learning/v1/policy.schema.json +836 -28
- package/schemas/learning/v1/reward.schema.json +520 -0
- package/schemas/learning/v1/reward_check.schema.json +9 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { assertHumanTaskProposal, redactHumanTaskProposal } from "./task-proposals.js";
|
|
2
|
+
import { contentHash } from "@openpond/harness";
|
|
3
|
+
import { LearningDomainError } from "../learning/errors.js";
|
|
4
|
+
import { assertHumanAnswers, HumanReviewCommandSchema, HumanReviewSchema, sealHumanRecord } from "./contracts.js";
|
|
5
|
+
const closed = (r) => ["accepted", "rejected", "cancelled"].includes(r.status);
|
|
6
|
+
function fail(code, status = 422) { throw new LearningDomainError(code, status); }
|
|
7
|
+
function answers(record, value, complete) {
|
|
8
|
+
try {
|
|
9
|
+
assertHumanAnswers(record.form, value, complete);
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
fail(e instanceof Error ? e.message : "human_answer_invalid");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function createHumanReviewService(repository, authority, now = () => new Date().toISOString()) {
|
|
16
|
+
async function access(context, record) {
|
|
17
|
+
if (record.scope !== context.scope)
|
|
18
|
+
fail("human_scope_denied", 403);
|
|
19
|
+
await authority.member(context);
|
|
20
|
+
const owner = await authority.owner(context, record.projectId);
|
|
21
|
+
const manager = owner || record.policy.managerIds.includes(context.actorId);
|
|
22
|
+
const reviewer = owner || record.assigneeId === context.actorId || record.policy.reviewerIds.includes(context.actorId);
|
|
23
|
+
const readable = owner || manager || reviewer || record.policy.teamVisible;
|
|
24
|
+
if (!readable)
|
|
25
|
+
fail("human_review_not_found", 404);
|
|
26
|
+
await authority.evidence(context, record);
|
|
27
|
+
return { owner, manager, reviewer };
|
|
28
|
+
}
|
|
29
|
+
async function command(context, raw) {
|
|
30
|
+
const input = HumanReviewCommandSchema.parse(raw);
|
|
31
|
+
return repository.transaction(context.scope, async (tx) => {
|
|
32
|
+
await authority.member(context);
|
|
33
|
+
let current = await tx.get(input.id);
|
|
34
|
+
let allowed = { owner: false, manager: false, reviewer: false };
|
|
35
|
+
if (input.action === "create") {
|
|
36
|
+
const spec = { projectId: input.projectId, evidence: input.evidence, form: input.form };
|
|
37
|
+
if (!await authority.owner(context, input.projectId))
|
|
38
|
+
fail("human_assignment_owner_required", 403);
|
|
39
|
+
await authority.evidence(context, spec);
|
|
40
|
+
for (const actorId of new Set([...(input.assigneeId ? [input.assigneeId] : []), ...input.policy.managerIds, ...input.policy.reviewerIds]))
|
|
41
|
+
await authority.assignee(context, actorId, spec);
|
|
42
|
+
if (input.kind === "grade" && !input.evidence.attempts.length)
|
|
43
|
+
fail("human_grade_requires_attempt");
|
|
44
|
+
if (input.kind !== "grade" && !input.evidence.taskIds?.length)
|
|
45
|
+
fail("human_task_assignment_requires_tasks");
|
|
46
|
+
if ((input.form.mode === "pairwise") !== (input.evidence.attempts.length === 2))
|
|
47
|
+
fail("human_form_evidence_mode_mismatch");
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (!current)
|
|
51
|
+
fail("human_review_not_found", 404);
|
|
52
|
+
allowed = await access(context, current);
|
|
53
|
+
if (input.action === "bind_author_publication" && !allowed.owner)
|
|
54
|
+
fail("human_task_publication_owner_required", 403);
|
|
55
|
+
if (["reassign", "cancel", "decide"].includes(input.action) && !allowed.manager)
|
|
56
|
+
fail("human_manager_required", 403);
|
|
57
|
+
if (["save_draft", "submit", "reserve_execution", "bind_execution"].includes(input.action) && !allowed.reviewer && !(input.action === "bind_execution" && current.status === "cancelled" && allowed.manager))
|
|
58
|
+
fail("human_assignee_required", 403);
|
|
59
|
+
if (input.action === "claim" && (!current.policy.queueClaim || !current.policy.teamVisible || (current.policy.reviewerIds.length && !current.policy.reviewerIds.includes(context.actorId) && !allowed.owner)))
|
|
60
|
+
fail("human_claim_denied", 403);
|
|
61
|
+
}
|
|
62
|
+
// Authority and evidence are checked before replaying a receipt. Revocation cannot be bypassed by retries.
|
|
63
|
+
const operationKey = contentHash([context.actorId, input.operationId]);
|
|
64
|
+
const requestHash = contentHash(input);
|
|
65
|
+
const receipt = await tx.operation(operationKey);
|
|
66
|
+
if (receipt) {
|
|
67
|
+
if (receipt.requestHash !== requestHash)
|
|
68
|
+
fail("human_operation_conflict", 409);
|
|
69
|
+
const result = await tx.get(receipt.reviewId, receipt.revision);
|
|
70
|
+
if (!result)
|
|
71
|
+
fail("human_operation_result_unavailable", 409);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
if ((current?.revision ?? 0) !== input.expectedRevision)
|
|
75
|
+
fail("human_revision_conflict", 409);
|
|
76
|
+
const timestamp = now();
|
|
77
|
+
if (input.action === "create") {
|
|
78
|
+
if (current || input.expectedRevision !== 0)
|
|
79
|
+
fail("human_review_exists", 409);
|
|
80
|
+
current = HumanReviewSchema.parse(sealHumanRecord({ schemaVersion: "openpond.humanReview.v1", id: input.id, revision: 1, scope: context.scope, projectId: input.projectId, createdBy: context.actorId, kind: input.kind, title: input.title, ...(input.dueAt ? { dueAt: input.dueAt } : {}), authorPublication: null, evidence: input.evidence, form: input.form, policy: input.policy, status: input.assigneeId ? "assigned" : "queued", assigneeId: input.assigneeId, generation: 1, drafts: [], submissions: [], decisions: [], audit: [], execution: null, createdAt: timestamp, updatedAt: timestamp }));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
if (!current)
|
|
84
|
+
fail("human_review_not_found", 404);
|
|
85
|
+
if (closed(current) && input.action !== "bind_author_publication" && !(current.status === "cancelled" && input.action === "bind_execution"))
|
|
86
|
+
fail("human_review_closed", 409);
|
|
87
|
+
const { contentHash: _hash, ...previous } = current;
|
|
88
|
+
let next = { ...previous, revision: current.revision + 1, updatedAt: timestamp };
|
|
89
|
+
if (input.action === "bind_author_publication") {
|
|
90
|
+
if (current.kind !== "author" || current.status !== "accepted" || current.decisions.at(-1)?.taskProposalHash !== input.proposalHash || current.authorPublication)
|
|
91
|
+
fail("human_task_publication_stale", 409);
|
|
92
|
+
if (!authority.publication)
|
|
93
|
+
fail("human_task_publication_owner_unavailable", 422);
|
|
94
|
+
await authority.publication(context, current, input.dataset, input.proposalHash);
|
|
95
|
+
next.authorPublication = { proposalHash: input.proposalHash, dataset: input.dataset, publishedBy: context.actorId, operationId: input.operationId, publishedAt: timestamp };
|
|
96
|
+
}
|
|
97
|
+
else if (input.action === "claim") {
|
|
98
|
+
if (current.assigneeId || current.status !== "queued")
|
|
99
|
+
fail("human_already_claimed", 409);
|
|
100
|
+
await authority.assignee(context, context.actorId, current);
|
|
101
|
+
next = { ...next, assigneeId: context.actorId, status: "assigned", generation: current.generation + 1, audit: [...current.audit, { actorId: context.actorId, action: "claim", priorAssigneeId: current.assigneeId, assigneeId: context.actorId, generation: current.generation + 1, note: "", occurredAt: timestamp }] };
|
|
102
|
+
}
|
|
103
|
+
else if (input.action === "reserve_execution" || input.action === "bind_execution") {
|
|
104
|
+
if (current.kind !== "execute" || input.generation !== current.generation)
|
|
105
|
+
fail("human_execution_assignment_stale", 409);
|
|
106
|
+
if (input.action === "reserve_execution") {
|
|
107
|
+
if (current.execution)
|
|
108
|
+
fail("human_execution_already_reserved", 409);
|
|
109
|
+
next.execution = { executionId: null, operationId: input.runOperationId, boundBy: context.actorId, configurationHash: input.configurationHash, generation: input.generation, result: null };
|
|
110
|
+
next.status = "in_progress";
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const prior = current.execution;
|
|
114
|
+
if (!prior || prior.executionId || prior.operationId !== input.runOperationId || prior.configurationHash !== input.configurationHash || (prior.boundBy !== context.actorId && !(current.status === "cancelled" && allowed.manager)) || prior.generation !== input.generation)
|
|
115
|
+
fail("human_execution_binding_conflict", 409);
|
|
116
|
+
const binding = { ...prior, executionId: input.executionId };
|
|
117
|
+
if (!authority.execution)
|
|
118
|
+
fail("human_execution_owner_unavailable", 422);
|
|
119
|
+
await authority.execution(context, current, binding);
|
|
120
|
+
next.execution = binding;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (input.action === "reassign") {
|
|
124
|
+
if (current.execution && !current.execution.result)
|
|
125
|
+
fail("human_execution_transfer_requires_completion", 409);
|
|
126
|
+
if (input.assigneeId)
|
|
127
|
+
await authority.assignee(context, input.assigneeId, current);
|
|
128
|
+
next = { ...next, execution: null, assigneeId: input.assigneeId, generation: current.generation + 1, status: input.assigneeId ? "assigned" : "queued", audit: [...current.audit, { actorId: context.actorId, action: "reassign", priorAssigneeId: current.assigneeId, assigneeId: input.assigneeId, generation: current.generation + 1, note: input.note, occurredAt: timestamp }] };
|
|
129
|
+
}
|
|
130
|
+
else if (input.action === "cancel") {
|
|
131
|
+
next.status = "cancelled";
|
|
132
|
+
next.audit = [...current.audit, { actorId: context.actorId, action: "cancel", priorAssigneeId: current.assigneeId, assigneeId: current.assigneeId, generation: current.generation, note: input.note, occurredAt: timestamp }];
|
|
133
|
+
}
|
|
134
|
+
else if (input.action === "save_draft" || input.action === "submit") {
|
|
135
|
+
if (current.kind === "execute" && !current.execution?.executionId)
|
|
136
|
+
fail("human_execution_not_bound", 409);
|
|
137
|
+
if (input.generation !== current.generation)
|
|
138
|
+
fail("human_assignment_generation_stale", 409);
|
|
139
|
+
const latestDecision = current.decisions.at(-1);
|
|
140
|
+
const currentSubmission = current.submissions.filter(s => s.actorId === context.actorId && s.generation === current.generation).at(-1);
|
|
141
|
+
if (currentSubmission && !(latestDecision?.outcome === "request_changes" && latestDecision.submissions.some(s => s.id === currentSubmission.id)))
|
|
142
|
+
fail("human_submission_already_sealed", 409);
|
|
143
|
+
if (input.taskProposal?.tasks.some(task => task.expectedOutput !== undefined) && !allowed.owner)
|
|
144
|
+
fail("human_reference_edit_owner_required", 403);
|
|
145
|
+
if (input.taskProposal || input.action === "submit") {
|
|
146
|
+
try {
|
|
147
|
+
assertHumanTaskProposal(current, input.taskProposal);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
fail(e instanceof Error ? e.message : "human_task_proposal_invalid");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
answers(current, input.answers, input.action === "submit");
|
|
154
|
+
if (input.action === "save_draft") {
|
|
155
|
+
next.drafts = [...current.drafts.filter(d => d.actorId !== context.actorId), { actorId: context.actorId, generation: current.generation, ...(input.taskProposal ? { taskProposal: input.taskProposal } : {}), answers: input.answers, note: input.note, updatedAt: timestamp }];
|
|
156
|
+
next.status = current.status === "returned" ? "returned" : "in_progress";
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
if (current.kind === "execute") {
|
|
160
|
+
if (!input.executionResult || !authority.completeExecution)
|
|
161
|
+
fail("human_execution_result_required", 409);
|
|
162
|
+
await authority.completeExecution(context, current, input.executionResult);
|
|
163
|
+
next.execution = { ...current.execution, result: input.executionResult };
|
|
164
|
+
}
|
|
165
|
+
if (input.evidenceHash !== contentHash(current.evidence) || input.formHash !== contentHash(current.form))
|
|
166
|
+
fail("human_review_snapshot_stale", 409);
|
|
167
|
+
const submission = sealHumanRecord({ ...(input.taskProposal ? { taskProposal: input.taskProposal } : {}), id: `submission-${operationKey}`, revision: 1, actorId: context.actorId, generation: current.generation, evidenceHash: input.evidenceHash, formHash: input.formHash, answers: input.answers, note: input.note, submittedAt: timestamp, supersedes: currentSubmission?.id ?? null });
|
|
168
|
+
next.submissions = [...current.submissions, submission];
|
|
169
|
+
next.drafts = current.drafts.filter(d => d.actorId !== context.actorId);
|
|
170
|
+
next.status = current.policy.approval === "none" ? "accepted" : "submitted";
|
|
171
|
+
if (current.policy.approval === "none")
|
|
172
|
+
next.decisions = [...current.decisions, sealHumanRecord({ id: `decision-${operationKey}`, revision: 1, actorId: context.actorId, outcome: "accept", ...(input.taskProposal ? { taskProposalHash: input.taskProposal.contentHash } : {}), submissions: [{ id: submission.id, contentHash: submission.contentHash }], answers: input.answers, note: "", decidedAt: timestamp })];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (input.action === "decide") {
|
|
176
|
+
if (current.policy.approval !== "manager" || current.status !== "submitted")
|
|
177
|
+
fail("human_decision_not_required", 409);
|
|
178
|
+
const selected = input.submissions.map(ref => current.submissions.find(s => s.id === ref.id && s.contentHash === ref.contentHash && s.generation === current.generation && !current.submissions.some(newer => newer.supersedes === s.id)) ?? fail("human_submission_revision_stale", 409));
|
|
179
|
+
if (current.policy.distinctManager && selected.some(s => s.actorId === context.actorId))
|
|
180
|
+
fail("human_distinct_manager_required", 403);
|
|
181
|
+
if (new Set(selected.map(s => s.id)).size !== selected.length)
|
|
182
|
+
fail("human_duplicate_submission");
|
|
183
|
+
if (input.outcome === "accept" && new Set(selected.map(s => s.actorId)).size < current.policy.minimumRaters)
|
|
184
|
+
fail("human_more_raters_required", 409);
|
|
185
|
+
if (input.outcome !== "accept" && !input.note.trim())
|
|
186
|
+
fail("human_decision_reason_required");
|
|
187
|
+
if (current.kind === "author" && input.outcome === "accept" && !selected.some(s => s.taskProposal?.contentHash === input.taskProposalHash))
|
|
188
|
+
fail("human_task_proposal_decision_required", 409);
|
|
189
|
+
if (current.kind !== "author" && input.taskProposalHash)
|
|
190
|
+
fail("human_task_proposal_unexpected");
|
|
191
|
+
answers(current, input.answers, input.outcome === "accept");
|
|
192
|
+
next.decisions = [...current.decisions, sealHumanRecord({ ...(input.taskProposalHash ? { taskProposalHash: input.taskProposalHash } : {}), id: `decision-${operationKey}`, revision: 1, actorId: context.actorId, outcome: input.outcome, submissions: input.submissions, answers: input.answers, note: input.note, decidedAt: timestamp })];
|
|
193
|
+
next.status = input.outcome === "accept" ? "accepted" : input.outcome === "reject" ? "rejected" : "returned";
|
|
194
|
+
}
|
|
195
|
+
current = HumanReviewSchema.parse(sealHumanRecord(next));
|
|
196
|
+
}
|
|
197
|
+
await tx.put(current, input.expectedRevision);
|
|
198
|
+
await tx.saveOperation(operationKey, { requestHash, reviewId: current.id, revision: current.revision });
|
|
199
|
+
return current;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async function get(context, id, revision) {
|
|
203
|
+
return repository.transaction(context.scope, async (tx) => {
|
|
204
|
+
const current = await tx.get(id);
|
|
205
|
+
if (!current)
|
|
206
|
+
fail("human_review_not_found", 404);
|
|
207
|
+
await access(context, current);
|
|
208
|
+
const result = revision === undefined ? current : await tx.get(id, revision);
|
|
209
|
+
if (!result)
|
|
210
|
+
fail("human_review_not_found", 404);
|
|
211
|
+
await authority.evidence(context, result);
|
|
212
|
+
// Historical snapshots retain prior attribution but cannot reinstate earlier access grants.
|
|
213
|
+
return result;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async function inbox(context, query) {
|
|
217
|
+
await authority.member(context);
|
|
218
|
+
return repository.transaction(context.scope, async (tx) => {
|
|
219
|
+
let cursor;
|
|
220
|
+
const permitted = [];
|
|
221
|
+
const actionable = new Set();
|
|
222
|
+
let approvalAvailable = false;
|
|
223
|
+
do {
|
|
224
|
+
const page = await tx.list({ projectId: query.projectId, afterId: cursor, limit: 100 });
|
|
225
|
+
for (const record of page.items) {
|
|
226
|
+
let rights;
|
|
227
|
+
try {
|
|
228
|
+
rights = await access(context, record);
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
if (e instanceof LearningDomainError && [403, 404].includes(e.status))
|
|
232
|
+
continue;
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
const active = !closed(record);
|
|
236
|
+
const directlyAssigned = record.assigneeId === context.actorId || record.policy.reviewerIds.includes(context.actorId) || rights.owner && record.assigneeId === null && record.createdBy === context.actorId;
|
|
237
|
+
const myWork = directlyAssigned && rights.reviewer && active && !record.submissions.some(s => s.actorId === context.actorId && s.generation === record.generation && record.status !== "returned");
|
|
238
|
+
const approval = rights.manager && record.policy.approval === "manager" && record.status === "submitted";
|
|
239
|
+
if (rights.manager && record.policy.approval === "manager")
|
|
240
|
+
approvalAvailable = true;
|
|
241
|
+
if (myWork || approval)
|
|
242
|
+
actionable.add(record.id);
|
|
243
|
+
const match = query.view === "mine" ? directlyAssigned : query.view === "approval" ? rights.manager && record.policy.approval === "manager" : record.policy.teamVisible || rights.manager;
|
|
244
|
+
if (match && (query.status === "active" ? active : !active) && (!query.afterId || record.id > query.afterId))
|
|
245
|
+
permitted.push(record);
|
|
246
|
+
}
|
|
247
|
+
cursor = page.nextCursor ?? undefined;
|
|
248
|
+
} while (cursor);
|
|
249
|
+
const items = permitted.slice(0, query.limit);
|
|
250
|
+
return { items, nextCursor: permitted.length > query.limit ? items.at(-1).id : null, actionCount: actionable.size, approvalAvailable };
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async function view(context, record) {
|
|
254
|
+
const current = await get(context, record.id);
|
|
255
|
+
const rights = await access(context, current);
|
|
256
|
+
const privileged = rights.owner || rights.manager;
|
|
257
|
+
const blind = !privileged && current.form.mode === "pairwise" && current.policy.blindPair;
|
|
258
|
+
const independent = !privileged && current.policy.independentRaters;
|
|
259
|
+
return { ...record, evidence: blind ? null : record.evidence, evidenceHash: contentHash(record.evidence), formHash: contentHash(record.form),
|
|
260
|
+
drafts: record.drafts.filter(d => d.actorId === context.actorId).map(d => ({ ...d, taskProposal: rights.owner ? d.taskProposal : redactHumanTaskProposal(d.taskProposal) })),
|
|
261
|
+
submissions: (independent ? record.submissions.filter(s => s.actorId === context.actorId) : record.submissions).map(s => ({ ...s, taskProposal: rights.owner ? s.taskProposal : redactHumanTaskProposal(s.taskProposal) })),
|
|
262
|
+
decisions: independent && !closed(current) ? [] : record.decisions,
|
|
263
|
+
audit: privileged ? record.audit : [], createdBy: blind ? "owner" : record.createdBy,
|
|
264
|
+
permissions: { ...rights, claim: current.status === "queued" && current.policy.queueClaim && current.policy.teamVisible && (!current.policy.reviewerIds.length || current.policy.reviewerIds.includes(context.actorId) || rights.owner) },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return { command, get, inbox, view };
|
|
268
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { contentHash, ReleaseIdSchema, ReleaseHashSchema } from "@openpond/harness";
|
|
3
|
+
import { assertBoundedTaskJson } from "../task-schema.js";
|
|
4
|
+
export const HumanTaskPatchSchema = z.object({ taskId: ReleaseIdSchema, input: z.record(z.string(), z.unknown()), policyVisibleContext: z.record(z.string(), z.unknown()), expectedOutput: z.record(z.string(), z.unknown()).nullable().optional(), tags: z.array(ReleaseIdSchema).max(100).optional() }).strict();
|
|
5
|
+
export const HumanTaskProposalSchema = z.object({ schemaVersion: z.literal("openpond.humanTaskProposal.v1"), tasks: z.array(HumanTaskPatchSchema).min(1).max(1000), contentHash: ReleaseHashSchema }).strict().superRefine((value, context) => { const { contentHash: hash, ...content } = value; if (hash !== contentHash(content))
|
|
6
|
+
context.addIssue({ code: "custom", message: "Task proposal content hash differs." }); if (new Set(value.tasks.map(t => t.taskId)).size !== value.tasks.length)
|
|
7
|
+
context.addIssue({ code: "custom", message: "Each task may be proposed once." }); try {
|
|
8
|
+
assertBoundedTaskJson(value, 1_048_576);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
context.addIssue({ code: "custom", message: "Task proposal exceeds retained data bounds." });
|
|
12
|
+
} });
|
|
13
|
+
export const HumanTaskProposalViewSchema = z.object({ schemaVersion: z.literal("openpond.humanTaskProposal.v1"), tasks: z.array(HumanTaskPatchSchema).min(1).max(1000), contentHash: ReleaseHashSchema, referenceRedacted: z.literal(true).optional() }).strict().superRefine((value, context) => { if (value.referenceRedacted) {
|
|
14
|
+
if (value.tasks.some(t => t.expectedOutput !== undefined))
|
|
15
|
+
context.addIssue({ code: "custom", message: "Redacted proposals cannot include reference outputs." });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const parsed = HumanTaskProposalSchema.safeParse(value);
|
|
19
|
+
if (!parsed.success)
|
|
20
|
+
context.addIssue({ code: "custom", message: "Task proposal integrity differs." });
|
|
21
|
+
} });
|
|
22
|
+
export function redactHumanTaskProposal(proposal) { if (!proposal || !proposal.tasks.some(t => t.expectedOutput !== undefined))
|
|
23
|
+
return proposal; return { ...proposal, referenceRedacted: true, tasks: proposal.tasks.map(task => { const { expectedOutput, ...publicTask } = task; void expectedOutput; return publicTask; }) }; }
|
|
24
|
+
export function sealHumanTaskProposal(tasks) { const value = { schemaVersion: "openpond.humanTaskProposal.v1", tasks }; return HumanTaskProposalSchema.parse({ ...value, contentHash: contentHash(value) }); }
|
|
25
|
+
export function assertHumanTaskProposal(record, proposal) { if (record.kind !== "author") {
|
|
26
|
+
if (proposal)
|
|
27
|
+
throw new Error("human_task_proposal_unexpected");
|
|
28
|
+
return;
|
|
29
|
+
} if (!proposal)
|
|
30
|
+
throw new Error("human_task_proposal_required"); const selected = record.evidence.taskIds ?? []; if (proposal.tasks.length !== selected.length || proposal.tasks.some(t => !selected.includes(t.taskId)))
|
|
31
|
+
throw new Error("human_task_proposal_selection_mismatch"); }
|
|
32
|
+
/** Task edits never rewrite the baseline source family, split, private refs,
|
|
33
|
+
* environment or grader contract. The host publishes through its existing owner. */
|
|
34
|
+
export function applyHumanTaskProposal(tasks, proposal) {
|
|
35
|
+
const patches = new Map(proposal.tasks.map(task => [task.taskId, task]));
|
|
36
|
+
if (proposal.tasks.some(patch => !tasks.some(task => task.id === patch.taskId)))
|
|
37
|
+
throw new Error("human_task_proposal_source_unavailable");
|
|
38
|
+
return tasks.map(task => { const patch = patches.get(task.id); return patch ? { ...task, input: patch.input, policyVisibleContext: patch.policyVisibleContext, ...(patch.expectedOutput === undefined ? {} : { expectedOutput: patch.expectedOutput }), ...(patch.tags === undefined ? {} : { tags: patch.tags }) } : task; });
|
|
39
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const JavaScriptVerifierResultSchema = z.object({
|
|
3
|
-
score: z.number().finite().min(0).max(1),
|
|
3
|
+
score: z.number().finite().min(0).max(1).nullable(),
|
|
4
4
|
passed: z.boolean(),
|
|
5
5
|
feedback: z.string().max(20_000),
|
|
6
6
|
evidenceRefs: z.array(z.string().max(240)).max(1_000).default([]),
|
|
7
|
-
}).strict();
|
|
7
|
+
}).strict().refine(value => value.score !== null || !value.passed, { message: "Unscorable verifiers cannot pass.", path: ["passed"] });
|
|
8
8
|
/** A process checker must never silently execute under different semantics. */
|
|
9
9
|
export function assertIsolatedVerifierRuntime(runtime) {
|
|
10
10
|
if (runtime === "sandbox_process")
|
|
@@ -18,8 +18,9 @@ export const RewardAuthoringFieldsSchema = z.object({
|
|
|
18
18
|
kind: z.enum(["custom_verifier", "state", "content", "schema", "artifact", "runtime_event", "model_judge", "learned_model", "human"]),
|
|
19
19
|
fields: z.string(), outputField: z.string(), expectedField: z.string(), expectedValue: z.string(),
|
|
20
20
|
schema: z.string(), reference: z.string(), events: z.string(), code: z.string(), exportName: z.string(), timeout: z.string(),
|
|
21
|
+
verifierRuntime: z.enum(["isolated_javascript", "sandbox_process"]).optional(),
|
|
21
22
|
rubric: z.string(), providerId: z.string(), modelId: z.string(), modelRevision: z.string(), temperature: z.string(),
|
|
22
|
-
reviewerRole: z.string(), learnedId: z.string(), learnedHash: z.string(), inputContract: z.string(), minimum: z.string(), maximum: z.string(),
|
|
23
|
+
reviewerRole: z.string(), humanForm: z.string().max(131_072).optional(), learnedId: z.string(), learnedHash: z.string(), inputContract: z.string(), minimum: z.string(), maximum: z.string(),
|
|
23
24
|
fixtures: z.array(RewardFixtureAuthoringFieldsSchema).max(50).optional(),
|
|
24
25
|
}).strict();
|
|
25
26
|
export const TaskFormatAuthoringFieldsSchema = z.object({
|