@openpond/harness 0.1.0
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 +31 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/RELEASING.md +44 -0
- package/dist/common.js +67 -0
- package/dist/harness-improvements.js +331 -0
- package/dist/harness-workspaces.js +370 -0
- package/dist/harness.js +90 -0
- package/dist/index.js +6 -0
- package/dist/models.js +10 -0
- package/dist/sha256.js +91 -0
- package/dist/tools.js +16 -0
- package/dist/types/common.d.ts +51 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/harness-improvements.d.ts +523 -0
- package/dist/types/harness-improvements.d.ts.map +1 -0
- package/dist/types/harness-workspaces.d.ts +812 -0
- package/dist/types/harness-workspaces.d.ts.map +1 -0
- package/dist/types/harness.d.ts +421 -0
- package/dist/types/harness.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/models.d.ts +11 -0
- package/dist/types/models.d.ts.map +1 -0
- package/dist/types/sha256.d.ts +2 -0
- package/dist/types/sha256.d.ts.map +1 -0
- package/dist/types/tools.d.ts +26 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/package.json +76 -0
package/CONTRACT.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Harness package boundary
|
|
2
|
+
|
|
3
|
+
`@openpond/harness` owns portable, host-neutral Harness identities and state
|
|
4
|
+
transitions. Host applications own persistence, model streaming, provider
|
|
5
|
+
sessions, authorization, credential leases, local processes, and artifact
|
|
6
|
+
bytes.
|
|
7
|
+
|
|
8
|
+
## Dependency direction
|
|
9
|
+
|
|
10
|
+
`@openpond/evals` may depend on and re-export `@openpond/harness`.
|
|
11
|
+
`@openpond/harness` must never import `@openpond/evals` or an application
|
|
12
|
+
package. This keeps Harness releases usable without installing an evaluation
|
|
13
|
+
runner.
|
|
14
|
+
|
|
15
|
+
## Compatibility
|
|
16
|
+
|
|
17
|
+
- Package semver and schema-version literals are independent.
|
|
18
|
+
- Immutable content never contains secrets, mutable database identifiers,
|
|
19
|
+
provider handles, UI state, or process handles.
|
|
20
|
+
- Changing a required field or content-hash identity requires a new schema
|
|
21
|
+
literal or an explicit normalizer.
|
|
22
|
+
- The initial support target is Node.js ESM on Node 22.14 through Node 24.
|
|
23
|
+
- Portable paths are at most 2,000 characters and individual portable assets
|
|
24
|
+
are at most 250 MB.
|
|
25
|
+
|
|
26
|
+
## Runtime ownership
|
|
27
|
+
|
|
28
|
+
The package describes Agent snapshots, releases, workspaces, overlays,
|
|
29
|
+
improvement evidence, tools, model identities, and traces. Evaluation execution
|
|
30
|
+
interfaces that bind a Harness to a Taskset and emit attempt receipts belong to
|
|
31
|
+
`@openpond/evals`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenPond
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# `@openpond/harness`
|
|
2
|
+
|
|
3
|
+
Portable contracts and pure helpers for OpenPond's mutable Harness:
|
|
4
|
+
|
|
5
|
+
- immutable Agent snapshots and Harness releases;
|
|
6
|
+
- content-addressed assets, artifacts, releases, and hashes;
|
|
7
|
+
- tool declarations and model identities;
|
|
8
|
+
- Harness workspaces, pinned run overlays, proposals, validation, advancement,
|
|
9
|
+
rollback, and merge receipts;
|
|
10
|
+
- improvement observations, Refiner outcomes, and apply receipts;
|
|
11
|
+
- model actions, tool observations, lifecycle events, and Harness traces.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
HarnessReleaseSchema,
|
|
16
|
+
HarnessRunOverlaySchema,
|
|
17
|
+
ImprovementObservationSchema,
|
|
18
|
+
contentHash,
|
|
19
|
+
} from "@openpond/harness";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Subpath exports are available at `/harness`, `/harness-improvements`,
|
|
23
|
+
`/harness-workspaces`, `/models`, and `/tools`.
|
|
24
|
+
|
|
25
|
+
This package does not run evaluations, grade outputs, persist product state,
|
|
26
|
+
execute a desktop or hosted session, or resolve credentials. Evaluation
|
|
27
|
+
Tasksets, runners, receipts, graders, and Work-evidence eligibility live in
|
|
28
|
+
`@openpond/evals`, which depends on this package.
|
|
29
|
+
|
|
30
|
+
## Verification
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm --dir packages/harness run check
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The check typechecks, tests, builds, scans the public dependency boundary,
|
|
37
|
+
installs the packed tarball into a clean consumer, verifies runtime and
|
|
38
|
+
TypeScript imports, and dry-run packs the public artifact.
|
package/RELEASING.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Releasing `@openpond/harness`
|
|
2
|
+
|
|
3
|
+
The package version is independent from OpenPond application and schema
|
|
4
|
+
versions. Version `0.1.0` is the initial public Harness release and supports
|
|
5
|
+
these initial schema literals:
|
|
6
|
+
|
|
7
|
+
- `openpond.agentSnapshot.v2`
|
|
8
|
+
- `openpond.harnessRelease.v2`
|
|
9
|
+
- `openpond.harnessTrace.v1`
|
|
10
|
+
|
|
11
|
+
## Trusted publishing
|
|
12
|
+
|
|
13
|
+
This package name is not published yet. Its first publication is a one-time npm
|
|
14
|
+
bootstrap after the Local branch is accepted and merged. The automated workflow
|
|
15
|
+
intentionally refuses to publish until the npm package exists. Complete the
|
|
16
|
+
organization's bootstrap procedure, configure `release-harness.yml` as
|
|
17
|
+
the trusted publisher, and verify package integrity and provenance before using
|
|
18
|
+
the normal release helper below. Do not bootstrap from a local feature branch.
|
|
19
|
+
|
|
20
|
+
To inspect an already-published version:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm harness:check
|
|
24
|
+
npm trust list @openpond/harness
|
|
25
|
+
npm view @openpond/harness version dist.integrity dist.attestations
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Later releases
|
|
29
|
+
|
|
30
|
+
For releases that do not already carry an intentional version bump, merge
|
|
31
|
+
feature work without changing the package version. When the intended package
|
|
32
|
+
changes are on `master`, prepare a separate release from a clean current
|
|
33
|
+
`master` checkout:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm release:harness:patch
|
|
37
|
+
# or release:harness:minor / release:harness:major
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The release helper creates a Harness-only release PR and updates the package
|
|
41
|
+
version and lockfile. Merging it triggers trusted publishing with provenance,
|
|
42
|
+
registry verification, and a package-specific tag. Record the registry
|
|
43
|
+
integrity/provenance evidence and merge commit in the release notes after
|
|
44
|
+
publication.
|
package/dist/common.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sha256Hex } from "./sha256.js";
|
|
3
|
+
export const MAX_PORTABLE_PATH_BYTES = 2_000;
|
|
4
|
+
export const MAX_PORTABLE_ASSET_BYTES = 250_000_000;
|
|
5
|
+
export const ReleaseIdSchema = z.string().trim().min(1).max(240);
|
|
6
|
+
export const ReleaseHashSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
7
|
+
export const ReleaseTimestampSchema = z.string().datetime({ offset: true });
|
|
8
|
+
export const MetadataSchema = z.record(z.string(), z.unknown()).default({});
|
|
9
|
+
export const ImmutableReleaseRefSchema = z.object({
|
|
10
|
+
id: ReleaseIdSchema,
|
|
11
|
+
contentHash: ReleaseHashSchema,
|
|
12
|
+
}).strict();
|
|
13
|
+
export const ImmutableAssetRefSchema = z.object({
|
|
14
|
+
id: ReleaseIdSchema,
|
|
15
|
+
path: z.string().trim().min(1).max(MAX_PORTABLE_PATH_BYTES).refine(safeRelativePath),
|
|
16
|
+
contentHash: ReleaseHashSchema,
|
|
17
|
+
sizeBytes: z.number().int().nonnegative().max(MAX_PORTABLE_ASSET_BYTES),
|
|
18
|
+
mediaType: z.string().trim().min(1).max(200),
|
|
19
|
+
visibility: z.enum(["policy", "verifier", "host_private"]),
|
|
20
|
+
}).strict();
|
|
21
|
+
export const ImmutableArtifactRefSchema = z.object({
|
|
22
|
+
id: ReleaseIdSchema,
|
|
23
|
+
contentHash: ReleaseHashSchema,
|
|
24
|
+
mediaType: z.string().trim().min(1).max(200).nullable().default(null),
|
|
25
|
+
sizeBytes: z.number().int().nonnegative().max(MAX_PORTABLE_ASSET_BYTES).nullable().default(null),
|
|
26
|
+
}).strict();
|
|
27
|
+
export const FailureClassSchema = z.enum([
|
|
28
|
+
"policy_failure",
|
|
29
|
+
"grader_failure",
|
|
30
|
+
"environment_failure",
|
|
31
|
+
"infrastructure_failure",
|
|
32
|
+
"timeout",
|
|
33
|
+
"cancelled",
|
|
34
|
+
]);
|
|
35
|
+
export function canonicalJson(value) {
|
|
36
|
+
return `${JSON.stringify(sortValue(value), null, 2)}\n`;
|
|
37
|
+
}
|
|
38
|
+
export function sha256(value) {
|
|
39
|
+
return sha256Hex(value);
|
|
40
|
+
}
|
|
41
|
+
export function contentHash(value) {
|
|
42
|
+
return sha256(canonicalJson(value));
|
|
43
|
+
}
|
|
44
|
+
export function withContentHash(value) {
|
|
45
|
+
return { ...value, contentHash: contentHash(value) };
|
|
46
|
+
}
|
|
47
|
+
export function assertContentHash(value, label) {
|
|
48
|
+
const { contentHash: actual, ...hashable } = value;
|
|
49
|
+
const expected = contentHash(hashable);
|
|
50
|
+
if (actual !== expected)
|
|
51
|
+
throw new Error(`${label} contentHash is ${actual}; expected ${expected}.`);
|
|
52
|
+
}
|
|
53
|
+
function safeRelativePath(value) {
|
|
54
|
+
const normalized = value.replaceAll("\\", "/");
|
|
55
|
+
if (!normalized || normalized.startsWith("/") || normalized.includes("\0"))
|
|
56
|
+
return false;
|
|
57
|
+
return !normalized.split("/").some((part) => !part || part === "." || part === "..");
|
|
58
|
+
}
|
|
59
|
+
function sortValue(value) {
|
|
60
|
+
if (Array.isArray(value))
|
|
61
|
+
return value.map(sortValue);
|
|
62
|
+
if (!value || typeof value !== "object")
|
|
63
|
+
return value;
|
|
64
|
+
return Object.fromEntries(Object.entries(value)
|
|
65
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
66
|
+
.map(([key, child]) => [key, sortValue(child)]));
|
|
67
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { HarnessImprovementRouteSchema, HarnessOverlaySnapshotRefSchema, } from "./harness-workspaces.js";
|
|
3
|
+
import { contentHash, ImmutableReleaseRefSchema, ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, } from "./common.js";
|
|
4
|
+
const BoundedTextSchema = z.string().trim().min(1).max(100_000);
|
|
5
|
+
const MetadataSchema = z.record(z.string(), z.unknown()).default({});
|
|
6
|
+
export const ImprovementSafeBoundaryKindSchema = z.enum([
|
|
7
|
+
"completed_tool_batch",
|
|
8
|
+
"before_model_step",
|
|
9
|
+
"turn_completed",
|
|
10
|
+
"turn_paused",
|
|
11
|
+
]);
|
|
12
|
+
export const ImprovementSafeBoundarySchema = z
|
|
13
|
+
.object({
|
|
14
|
+
kind: ImprovementSafeBoundaryKindSchema,
|
|
15
|
+
eventSequence: z.number().int().nonnegative(),
|
|
16
|
+
occurredAt: ReleaseTimestampSchema,
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
export const ImprovementEventRefSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
id: ReleaseIdSchema,
|
|
22
|
+
sequence: z.number().int().nonnegative().nullable(),
|
|
23
|
+
contentHash: ReleaseHashSchema,
|
|
24
|
+
})
|
|
25
|
+
.strict();
|
|
26
|
+
export const ImprovementObservationKindSchema = z.enum([
|
|
27
|
+
"tool_failure",
|
|
28
|
+
"retry",
|
|
29
|
+
"recovery",
|
|
30
|
+
"validation",
|
|
31
|
+
"user_turn",
|
|
32
|
+
"reusable_success",
|
|
33
|
+
"completion_detour",
|
|
34
|
+
]);
|
|
35
|
+
export const ImprovementObservationStateSchema = z.enum([
|
|
36
|
+
"open",
|
|
37
|
+
"recovered",
|
|
38
|
+
"terminal",
|
|
39
|
+
]);
|
|
40
|
+
export const ImprovementToolIdentitySchema = z
|
|
41
|
+
.object({
|
|
42
|
+
name: z.string().trim().min(1).max(500),
|
|
43
|
+
invocationKey: ReleaseHashSchema,
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
export const ImprovementObservationContentSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
schemaVersion: z.literal("openpond.improvementObservation.v1"),
|
|
49
|
+
id: ReleaseIdSchema,
|
|
50
|
+
runRef: ReleaseIdSchema,
|
|
51
|
+
turnId: ReleaseIdSchema,
|
|
52
|
+
harnessRelease: ImmutableReleaseRefSchema,
|
|
53
|
+
overlay: HarnessOverlaySnapshotRefSchema.nullable(),
|
|
54
|
+
eventRefs: z.array(ImprovementEventRefSchema).min(1).max(100),
|
|
55
|
+
kind: ImprovementObservationKindSchema,
|
|
56
|
+
state: ImprovementObservationStateSchema,
|
|
57
|
+
tool: ImprovementToolIdentitySchema.nullable(),
|
|
58
|
+
deterministicClass: z.string().trim().min(1).max(500).nullable(),
|
|
59
|
+
summary: BoundedTextSchema,
|
|
60
|
+
createdAt: ReleaseTimestampSchema,
|
|
61
|
+
metadata: MetadataSchema,
|
|
62
|
+
})
|
|
63
|
+
.strict()
|
|
64
|
+
.superRefine((observation, context) => {
|
|
65
|
+
if (new Set(observation.eventRefs.map((reference) => reference.id)).size !==
|
|
66
|
+
observation.eventRefs.length) {
|
|
67
|
+
context.addIssue({
|
|
68
|
+
code: "custom",
|
|
69
|
+
message: "observation event refs must be unique",
|
|
70
|
+
path: ["eventRefs"],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (["tool_failure", "retry", "recovery", "completion_detour"].includes(observation.kind) &&
|
|
74
|
+
observation.tool === null) {
|
|
75
|
+
context.addIssue({
|
|
76
|
+
code: "custom",
|
|
77
|
+
message: `${observation.kind} observations require a tool identity`,
|
|
78
|
+
path: ["tool"],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
export const ImprovementObservationSchema = ImprovementObservationContentSchema.extend({
|
|
83
|
+
contentHash: ReleaseHashSchema,
|
|
84
|
+
}).strict();
|
|
85
|
+
export const RefinementTriggerPolicySchema = z
|
|
86
|
+
.object({
|
|
87
|
+
schemaVersion: z.literal("openpond.refinementTriggerPolicy.v1"),
|
|
88
|
+
maxEstimatedCostUsd: z.number().finite().nonnegative(),
|
|
89
|
+
cooldownMs: z.number().int().nonnegative(),
|
|
90
|
+
maxPendingPlans: z.number().int().min(1).max(100),
|
|
91
|
+
maxEvidenceEvents: z.number().int().min(1).max(1_000),
|
|
92
|
+
maxProposalEdits: z.number().int().min(1).max(1_000),
|
|
93
|
+
maxProposalBytes: z.number().int().min(1).max(10_000_000),
|
|
94
|
+
})
|
|
95
|
+
.strict();
|
|
96
|
+
export const RefinementTriggerDecisionKindSchema = z.enum([
|
|
97
|
+
"no_action",
|
|
98
|
+
"route_deterministically",
|
|
99
|
+
"queue_refiner",
|
|
100
|
+
]);
|
|
101
|
+
export const RefinementTriggerDecisionContentSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
schemaVersion: z.literal("openpond.refinementTriggerDecision.v1"),
|
|
104
|
+
id: ReleaseIdSchema,
|
|
105
|
+
runRef: ReleaseIdSchema,
|
|
106
|
+
turnId: ReleaseIdSchema,
|
|
107
|
+
harnessRelease: ImmutableReleaseRefSchema,
|
|
108
|
+
overlay: HarnessOverlaySnapshotRefSchema.nullable(),
|
|
109
|
+
observations: z.array(ImmutableReleaseRefSchema).max(100),
|
|
110
|
+
decision: RefinementTriggerDecisionKindSchema,
|
|
111
|
+
deterministicRoute: HarnessImprovementRouteSchema.nullable(),
|
|
112
|
+
suggestedRoutes: z.array(HarnessImprovementRouteSchema).max(8),
|
|
113
|
+
reason: BoundedTextSchema,
|
|
114
|
+
deduplicationKey: ReleaseHashSchema,
|
|
115
|
+
policy: RefinementTriggerPolicySchema,
|
|
116
|
+
estimatedMaxCostUsd: z.number().finite().nonnegative(),
|
|
117
|
+
pendingPlanCount: z.number().int().nonnegative(),
|
|
118
|
+
boundary: ImprovementSafeBoundarySchema,
|
|
119
|
+
cooldownUntil: ReleaseTimestampSchema.nullable(),
|
|
120
|
+
createdAt: ReleaseTimestampSchema,
|
|
121
|
+
metadata: MetadataSchema,
|
|
122
|
+
})
|
|
123
|
+
.strict()
|
|
124
|
+
.superRefine((trigger, context) => {
|
|
125
|
+
if (trigger.estimatedMaxCostUsd > trigger.policy.maxEstimatedCostUsd) {
|
|
126
|
+
context.addIssue({
|
|
127
|
+
code: "custom",
|
|
128
|
+
message: "estimated Refiner cost exceeds the trigger policy budget",
|
|
129
|
+
path: ["estimatedMaxCostUsd"],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (trigger.pendingPlanCount > trigger.policy.maxPendingPlans) {
|
|
133
|
+
context.addIssue({
|
|
134
|
+
code: "custom",
|
|
135
|
+
message: "pending plan count exceeds the trigger policy limit",
|
|
136
|
+
path: ["pendingPlanCount"],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (trigger.observations.length > trigger.policy.maxEvidenceEvents) {
|
|
140
|
+
context.addIssue({
|
|
141
|
+
code: "custom",
|
|
142
|
+
message: "trigger observations exceed the evidence budget",
|
|
143
|
+
path: ["observations"],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (trigger.decision === "route_deterministically" &&
|
|
147
|
+
trigger.deterministicRoute === null) {
|
|
148
|
+
context.addIssue({
|
|
149
|
+
code: "custom",
|
|
150
|
+
message: "deterministic decisions require a route",
|
|
151
|
+
path: ["deterministicRoute"],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (trigger.decision !== "route_deterministically" &&
|
|
155
|
+
trigger.deterministicRoute !== null) {
|
|
156
|
+
context.addIssue({
|
|
157
|
+
code: "custom",
|
|
158
|
+
message: "only deterministic decisions may declare a deterministic route",
|
|
159
|
+
path: ["deterministicRoute"],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (trigger.decision === "no_action" && trigger.suggestedRoutes.length > 0) {
|
|
163
|
+
context.addIssue({
|
|
164
|
+
code: "custom",
|
|
165
|
+
message: "no-action decisions cannot suggest routes",
|
|
166
|
+
path: ["suggestedRoutes"],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (trigger.decision !== "no_action" && trigger.observations.length === 0) {
|
|
170
|
+
context.addIssue({
|
|
171
|
+
code: "custom",
|
|
172
|
+
message: "actionable trigger decisions require observations",
|
|
173
|
+
path: ["observations"],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
export const RefinementTriggerDecisionSchema = RefinementTriggerDecisionContentSchema.extend({
|
|
178
|
+
contentHash: ReleaseHashSchema,
|
|
179
|
+
}).strict();
|
|
180
|
+
export const ImprovementRouteAuthoritySchema = z.enum([
|
|
181
|
+
"runtime_service",
|
|
182
|
+
"refiner_model",
|
|
183
|
+
"human_review",
|
|
184
|
+
"evaluation_system",
|
|
185
|
+
"training_system",
|
|
186
|
+
]);
|
|
187
|
+
export const ImprovementRouteDecisionContentSchema = z
|
|
188
|
+
.object({
|
|
189
|
+
schemaVersion: z.literal("openpond.improvementRouteDecision.v1"),
|
|
190
|
+
id: ReleaseIdSchema,
|
|
191
|
+
trigger: ImmutableReleaseRefSchema,
|
|
192
|
+
route: HarnessImprovementRouteSchema,
|
|
193
|
+
authority: ImprovementRouteAuthoritySchema,
|
|
194
|
+
automatic: z.boolean(),
|
|
195
|
+
reason: BoundedTextSchema,
|
|
196
|
+
createdAt: ReleaseTimestampSchema,
|
|
197
|
+
metadata: MetadataSchema,
|
|
198
|
+
})
|
|
199
|
+
.strict()
|
|
200
|
+
.superRefine((decision, context) => {
|
|
201
|
+
if (decision.automatic &&
|
|
202
|
+
["human_review", "evaluation_system", "training_system"].includes(decision.authority)) {
|
|
203
|
+
context.addIssue({
|
|
204
|
+
code: "custom",
|
|
205
|
+
message: `${decision.authority} routes cannot be marked automatic`,
|
|
206
|
+
path: ["automatic"],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (decision.route === "training" &&
|
|
210
|
+
decision.automatic &&
|
|
211
|
+
decision.authority !== "training_system") {
|
|
212
|
+
context.addIssue({
|
|
213
|
+
code: "custom",
|
|
214
|
+
message: "automatic training routes require training-system authority",
|
|
215
|
+
path: ["authority"],
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
export const ImprovementRouteDecisionSchema = ImprovementRouteDecisionContentSchema.extend({
|
|
220
|
+
contentHash: ReleaseHashSchema,
|
|
221
|
+
}).strict();
|
|
222
|
+
export const HarnessRefinerOutcomeContentSchema = z
|
|
223
|
+
.object({
|
|
224
|
+
schemaVersion: z.literal("openpond.harnessRefinerOutcome.v1"),
|
|
225
|
+
id: ReleaseIdSchema,
|
|
226
|
+
trigger: ImmutableReleaseRefSchema,
|
|
227
|
+
decision: z.enum(["no_action", "proposed"]),
|
|
228
|
+
proposal: ImmutableReleaseRefSchema.nullable(),
|
|
229
|
+
reason: BoundedTextSchema,
|
|
230
|
+
evidenceRefs: z.array(ImmutableReleaseRefSchema).max(100),
|
|
231
|
+
estimatedCostUsd: z.number().finite().nonnegative(),
|
|
232
|
+
createdAt: ReleaseTimestampSchema,
|
|
233
|
+
metadata: MetadataSchema,
|
|
234
|
+
})
|
|
235
|
+
.strict()
|
|
236
|
+
.superRefine((outcome, context) => {
|
|
237
|
+
if ((outcome.decision === "proposed") !== (outcome.proposal !== null)) {
|
|
238
|
+
context.addIssue({
|
|
239
|
+
code: "custom",
|
|
240
|
+
message: "proposed Refiner outcomes require a proposal; no-action outcomes cannot include one",
|
|
241
|
+
path: ["proposal"],
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
export const HarnessRefinerOutcomeSchema = HarnessRefinerOutcomeContentSchema.extend({
|
|
246
|
+
contentHash: ReleaseHashSchema,
|
|
247
|
+
}).strict();
|
|
248
|
+
export const ImprovementApplyDecisionSchema = z.enum([
|
|
249
|
+
"applied",
|
|
250
|
+
"retained",
|
|
251
|
+
"declined",
|
|
252
|
+
"conflict",
|
|
253
|
+
"rolled_back",
|
|
254
|
+
]);
|
|
255
|
+
export const ImprovementApplyReceiptContentSchema = z
|
|
256
|
+
.object({
|
|
257
|
+
schemaVersion: z.literal("openpond.improvementApplyReceipt.v1"),
|
|
258
|
+
id: ReleaseIdSchema,
|
|
259
|
+
proposal: ImmutableReleaseRefSchema,
|
|
260
|
+
beforeOverlay: HarnessOverlaySnapshotRefSchema,
|
|
261
|
+
afterOverlay: HarnessOverlaySnapshotRefSchema.nullable(),
|
|
262
|
+
decision: ImprovementApplyDecisionSchema,
|
|
263
|
+
boundary: ImprovementSafeBoundarySchema,
|
|
264
|
+
validationRefs: z.array(ImmutableReleaseRefSchema).max(100),
|
|
265
|
+
outcomeEvidenceRefs: z.array(ImprovementEventRefSchema).max(1_000),
|
|
266
|
+
rollbackOf: ImmutableReleaseRefSchema.nullable(),
|
|
267
|
+
createdAt: ReleaseTimestampSchema,
|
|
268
|
+
metadata: MetadataSchema,
|
|
269
|
+
})
|
|
270
|
+
.strict()
|
|
271
|
+
.superRefine((receipt, context) => {
|
|
272
|
+
if ((receipt.decision === "applied") !== (receipt.afterOverlay !== null)) {
|
|
273
|
+
context.addIssue({
|
|
274
|
+
code: "custom",
|
|
275
|
+
message: "applied receipts require an after-overlay; other decisions cannot include one",
|
|
276
|
+
path: ["afterOverlay"],
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if ((receipt.decision === "rolled_back") !== (receipt.rollbackOf !== null)) {
|
|
280
|
+
context.addIssue({
|
|
281
|
+
code: "custom",
|
|
282
|
+
message: "only rollback receipts require rollbackOf",
|
|
283
|
+
path: ["rollbackOf"],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
export const ImprovementApplyReceiptSchema = ImprovementApplyReceiptContentSchema.extend({
|
|
288
|
+
contentHash: ReleaseHashSchema,
|
|
289
|
+
}).strict();
|
|
290
|
+
function createHashedContract(input) {
|
|
291
|
+
const parsed = input.contentSchema.parse(input.content);
|
|
292
|
+
return input.resultSchema.parse({
|
|
293
|
+
...parsed,
|
|
294
|
+
contentHash: contentHash(parsed),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
export function createImprovementObservation(content) {
|
|
298
|
+
return createHashedContract({
|
|
299
|
+
content,
|
|
300
|
+
contentSchema: ImprovementObservationContentSchema,
|
|
301
|
+
resultSchema: ImprovementObservationSchema,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
export function createRefinementTriggerDecision(content) {
|
|
305
|
+
return createHashedContract({
|
|
306
|
+
content,
|
|
307
|
+
contentSchema: RefinementTriggerDecisionContentSchema,
|
|
308
|
+
resultSchema: RefinementTriggerDecisionSchema,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
export function createImprovementRouteDecision(content) {
|
|
312
|
+
return createHashedContract({
|
|
313
|
+
content,
|
|
314
|
+
contentSchema: ImprovementRouteDecisionContentSchema,
|
|
315
|
+
resultSchema: ImprovementRouteDecisionSchema,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
export function createHarnessRefinerOutcome(content) {
|
|
319
|
+
return createHashedContract({
|
|
320
|
+
content,
|
|
321
|
+
contentSchema: HarnessRefinerOutcomeContentSchema,
|
|
322
|
+
resultSchema: HarnessRefinerOutcomeSchema,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
export function createImprovementApplyReceipt(content) {
|
|
326
|
+
return createHashedContract({
|
|
327
|
+
content,
|
|
328
|
+
contentSchema: ImprovementApplyReceiptContentSchema,
|
|
329
|
+
resultSchema: ImprovementApplyReceiptSchema,
|
|
330
|
+
});
|
|
331
|
+
}
|