loopforge 3.7.1 → 3.8.1
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/README.md +171 -169
- package/dist/audit.d.ts +53 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +106 -22
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +52 -67
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +134 -147
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +103 -4
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-facts.d.ts +13 -0
- package/dist/cognitive-facts.d.ts.map +1 -1
- package/dist/cognitive-facts.js +28 -13
- package/dist/cognitive-facts.js.map +1 -1
- package/dist/committed-round.d.ts +103 -21
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +188 -45
- package/dist/committed-round.js.map +1 -1
- package/dist/contract-items.d.ts +55 -0
- package/dist/contract-items.d.ts.map +1 -0
- package/dist/contract-items.js +180 -0
- package/dist/contract-items.js.map +1 -0
- package/dist/enforcement-gate.d.ts +20 -2
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +197 -386
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +53 -38
- package/dist/engine.js.map +1 -1
- package/dist/evidence-claims.d.ts +4 -4
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +16 -14
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-provider.d.ts +85 -89
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +328 -184
- package/dist/evidence-provider.js.map +1 -1
- package/dist/explain.d.ts +76 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +127 -0
- package/dist/explain.js.map +1 -0
- package/dist/generate-schema.js +152 -16
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +40 -53
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +229 -553
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-extras-parser.d.ts +0 -1
- package/dist/loop-extras-parser.d.ts.map +1 -1
- package/dist/loop-extras-parser.js +0 -4
- package/dist/loop-extras-parser.js.map +1 -1
- package/dist/loop-projection.d.ts.map +1 -1
- package/dist/loop-projection.js +3 -1
- package/dist/loop-projection.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +30 -21
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +47 -103
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +51 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +39 -23
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +148 -163
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +89 -0
- package/dist/mcp/submission-boundary.d.ts.map +1 -0
- package/dist/mcp/submission-boundary.js +203 -0
- package/dist/mcp/submission-boundary.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +249 -128
- package/dist/mcp/tools.js.map +1 -1
- package/dist/policy-metrics.js +2 -2
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +55 -93
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +149 -38
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +1 -32
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +282 -435
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-policy.d.ts +1 -4
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +6 -7
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +365 -142
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +21 -66
- package/dist/protocol.js.map +1 -1
- package/dist/replay.js +2 -2
- package/dist/replay.js.map +1 -1
- package/dist/round-contract.d.ts +69 -57
- package/dist/round-contract.d.ts.map +1 -1
- package/dist/round-contract.js +104 -64
- package/dist/round-contract.js.map +1 -1
- package/dist/round-coordinator.d.ts +36 -16
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +166 -102
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +3 -5
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +0 -1
- package/dist/round-driver.js.map +1 -1
- package/dist/round-facts.d.ts +96 -0
- package/dist/round-facts.d.ts.map +1 -0
- package/dist/round-facts.js +216 -0
- package/dist/round-facts.js.map +1 -0
- package/dist/round-transaction.d.ts +19 -9
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +90 -22
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +87 -9
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +396 -27
- package/dist/self-eval.js.map +1 -1
- package/dist/subgoal-state.d.ts +77 -0
- package/dist/subgoal-state.d.ts.map +1 -0
- package/dist/subgoal-state.js +198 -0
- package/dist/subgoal-state.js.map +1 -0
- package/dist/token-utils.d.ts +52 -19
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +83 -72
- package/dist/token-utils.js.map +1 -1
- package/dist/verification-gate.d.ts +14 -31
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +178 -496
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace.d.ts +9 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +17 -0
- package/dist/workspace.js.map +1 -1
- package/loop_policy.json +6 -30
- package/package.json +1 -1
- package/skills/perception/SKILL.md +243 -79
package/dist/self-eval.d.ts
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
/** Structured self-evaluation parsing and normalization — pure functions.
|
|
2
2
|
* The MCP boundary validates required fields before these helpers run.
|
|
3
3
|
*/
|
|
4
|
-
import { type CriterionRevision, type
|
|
5
|
-
/**
|
|
6
|
-
|
|
4
|
+
import { type CriterionRevision, type ExecutionReport, type PromptRequests, type RoundContractProposal, type RoundOutcome, type SelfEvaluation, type SubGoalUpdate } from "./protocol.js";
|
|
5
|
+
/** v3.8.1: the emerged-subtask intake bound — ONE constant, because the
|
|
6
|
+
* sub-goal ids are derived by ORDINAL over this list. The strict submission
|
|
7
|
+
* boundary builds the reference space the agent may transition from, and the
|
|
8
|
+
* lenient normalizer builds the committed list every prompt compiles from;
|
|
9
|
+
* bounding them differently let the boundary accept a transition to a
|
|
10
|
+
* sub-goal the prompt could never carry, and the migration was then dropped
|
|
11
|
+
* by the compile path without a word. */
|
|
12
|
+
export declare const EMERGED_LIMITS: {
|
|
13
|
+
readonly items: 50;
|
|
14
|
+
readonly chars: 500;
|
|
15
|
+
};
|
|
16
|
+
/** THE emerged-list normalization. Both the boundary and the committed
|
|
17
|
+
* evaluation call this, so the two sets cannot disagree. */
|
|
18
|
+
export declare function boundedEmergedSubtasks(value: unknown): string[];
|
|
19
|
+
/** Parse ExecutionReport from a raw JSON object. */
|
|
20
|
+
export declare function parseExecutionReport(raw: Record<string, unknown> | undefined | null): ExecutionReport | undefined;
|
|
21
|
+
/** v3.8: The criterion ids the agent claims met this round. */
|
|
22
|
+
export declare function claimedMetCriteria(report: {
|
|
23
|
+
criterion_claims?: Array<{
|
|
24
|
+
criterion_id: string;
|
|
25
|
+
outcome: string;
|
|
26
|
+
}>;
|
|
27
|
+
} | undefined | null): string[];
|
|
28
|
+
/** v3.8: The criterion ids the agent declares still outstanding. */
|
|
29
|
+
export declare function claimedRemainingCriteria(report: {
|
|
30
|
+
criterion_claims?: Array<{
|
|
31
|
+
criterion_id: string;
|
|
32
|
+
outcome: string;
|
|
33
|
+
}>;
|
|
34
|
+
} | undefined | null): string[];
|
|
7
35
|
/** Parse CriterionRevision[] from a raw JSON array. */
|
|
8
36
|
export declare function parseCriterionRevisions(raw: unknown): CriterionRevision[];
|
|
9
37
|
/** Parse SubGoalUpdate[] from a raw JSON array.
|
|
@@ -65,15 +93,65 @@ export declare function validateSubGoalUpdatesShape(raw: Record<string, unknown>
|
|
|
65
93
|
reason: string;
|
|
66
94
|
detail: string;
|
|
67
95
|
}>;
|
|
96
|
+
export interface ContractValidationError {
|
|
97
|
+
field: string;
|
|
98
|
+
reason: string;
|
|
99
|
+
detail: string;
|
|
100
|
+
}
|
|
101
|
+
/** v3.8: Everything the strict contract boundary needs to judge a declaration
|
|
102
|
+
* against the SAME derived state the agent saw in its prompt. Injected rather
|
|
103
|
+
* than imported so the validator stays a pure, unit-testable function. */
|
|
104
|
+
export interface ContractValidationContext {
|
|
105
|
+
/** rci- ids of the ACTIVE contract; null when it cannot be observed (fail
|
|
106
|
+
* open — shape checks stay strict either way). */
|
|
107
|
+
activeItemIds: ReadonlySet<string> | null;
|
|
108
|
+
/** sg- ids the agent may legitimately reference: the compiled set plus the
|
|
109
|
+
* submission's own same-round `emerged_subtasks`; null when it cannot be
|
|
110
|
+
* observed (fail open). */
|
|
111
|
+
knownSubGoalIds: ReadonlySet<string> | null;
|
|
112
|
+
/** Configured AND enabled AND after-capable evidence command. */
|
|
113
|
+
isConfiguredCommand: (name: string) => boolean;
|
|
114
|
+
/** Returns a detail string when a declared scope entry leaves the
|
|
115
|
+
* workspace, or null when it is contained. */
|
|
116
|
+
checkScopeEntry: (entry: string) => string | null;
|
|
117
|
+
}
|
|
118
|
+
/** v3.8: Strict STRUCTURAL boundary for the Round Contract declaration and
|
|
119
|
+
* the contract item claims — the same pre-advance contract as
|
|
120
|
+
* subgoal_updates: an error here is a payload defect, never a work-quality
|
|
121
|
+
* rejection (no session state, no gates, no rejection counters, retry with
|
|
122
|
+
* the same roundId as `contract_invalid`).
|
|
123
|
+
*
|
|
124
|
+
* Declaration strictness: items must exist and stay within CONTRACT_LIMITS;
|
|
125
|
+
* every item must bind at least one configured, enabled, after-capable
|
|
126
|
+
* command; scope entries must stay inside the workspace; criterion and
|
|
127
|
+
* sub-goal references must be well-formed, and sub-goal references must name
|
|
128
|
+
* a sub-goal that actually exists. The limits are enforced HERE rather than
|
|
129
|
+
* by silently truncating in the lenient parser — a declaration is a
|
|
130
|
+
* boundary, not a suggestion.
|
|
131
|
+
*
|
|
132
|
+
* Claim strictness: item ids must be well formed, unique, and — when the
|
|
133
|
+
* active contract is known — reference an item of the ACTIVE contract. */
|
|
134
|
+
export declare function validateContractShape(raw: Record<string, unknown>, context: ContractValidationContext): ContractValidationError[];
|
|
68
135
|
/** Build a SelfEvaluation from a parsed JSON object.
|
|
69
136
|
* Lenient parsing: missing optional fields get sensible defaults. */
|
|
70
137
|
export declare function buildSelfEvaluation(raw: Record<string, unknown>): SelfEvaluation;
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
138
|
+
/** v3.8: The ONE set of contract-declaration limits. `validateContractShape`
|
|
139
|
+
* (the strict boundary) rejects a declaration over these; `parseRoundContract`
|
|
140
|
+
* (the lenient path, reached only after the strict boundary passed) truncates
|
|
141
|
+
* to the same numbers. One source, so the two can never drift. */
|
|
142
|
+
export declare const CONTRACT_LIMITS: {
|
|
143
|
+
readonly items: 20;
|
|
144
|
+
readonly scope: 50;
|
|
145
|
+
readonly criterionRefs: 20;
|
|
146
|
+
readonly subgoalRefs: 20;
|
|
147
|
+
readonly verifyWith: 20;
|
|
148
|
+
};
|
|
149
|
+
/** Parse a Round Contract PROPOSAL from raw JSON input. Lenient on shapes
|
|
150
|
+
* the strict declaration boundary already rejected: strings are trimmed and
|
|
151
|
+
* capped, non-string array entries are dropped. Item ORDER and COUNT are
|
|
152
|
+
* preserved — the derived rci- ids depend on them. Returns undefined only
|
|
153
|
+
* when the raw value is absent or not an object. */
|
|
154
|
+
export declare function parseRoundContract(raw: unknown): RoundContractProposal | undefined;
|
|
77
155
|
/** Parse a PromptRequests object from raw JSON input.
|
|
78
156
|
* Lenient: missing or invalid fields get sensible defaults.
|
|
79
157
|
* Returns undefined if the raw value is absent or not an object,
|
package/dist/self-eval.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self-eval.d.ts","sourceRoot":"","sources":["../src/self-eval.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"self-eval.d.ts","sourceRoot":"","sources":["../src/self-eval.ts"],"names":[],"mappings":"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"}
|
package/dist/self-eval.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Structured self-evaluation parsing and normalization — pure functions.
|
|
2
2
|
* The MCP boundary validates required fields before these helpers run.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { makeExecutionReport, makeSelfEvaluation, } from "./protocol.js";
|
|
5
5
|
// ── Raw parsing helpers ───────────────────────────────────────────────────
|
|
6
6
|
function boundedString(value, maxChars) {
|
|
7
7
|
return typeof value === "string" ? value.slice(0, maxChars) : undefined;
|
|
@@ -14,6 +14,19 @@ function boundedStringArray(value, maxItems, maxChars) {
|
|
|
14
14
|
.slice(0, maxItems)
|
|
15
15
|
: [];
|
|
16
16
|
}
|
|
17
|
+
/** v3.8.1: the emerged-subtask intake bound — ONE constant, because the
|
|
18
|
+
* sub-goal ids are derived by ORDINAL over this list. The strict submission
|
|
19
|
+
* boundary builds the reference space the agent may transition from, and the
|
|
20
|
+
* lenient normalizer builds the committed list every prompt compiles from;
|
|
21
|
+
* bounding them differently let the boundary accept a transition to a
|
|
22
|
+
* sub-goal the prompt could never carry, and the migration was then dropped
|
|
23
|
+
* by the compile path without a word. */
|
|
24
|
+
export const EMERGED_LIMITS = { items: 50, chars: 500 };
|
|
25
|
+
/** THE emerged-list normalization. Both the boundary and the committed
|
|
26
|
+
* evaluation call this, so the two sets cannot disagree. */
|
|
27
|
+
export function boundedEmergedSubtasks(value) {
|
|
28
|
+
return boundedStringArray(value, EMERGED_LIMITS.items, EMERGED_LIMITS.chars);
|
|
29
|
+
}
|
|
17
30
|
function nonNegativeInteger(value) {
|
|
18
31
|
return typeof value === "number" && Number.isFinite(value)
|
|
19
32
|
? Math.max(0, Math.trunc(value))
|
|
@@ -40,17 +53,17 @@ function parseRetroactiveClaims(raw) {
|
|
|
40
53
|
}
|
|
41
54
|
return claims;
|
|
42
55
|
}
|
|
43
|
-
/** Parse
|
|
44
|
-
export function
|
|
56
|
+
/** Parse ExecutionReport from a raw JSON object. */
|
|
57
|
+
export function parseExecutionReport(raw) {
|
|
45
58
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
46
59
|
return undefined;
|
|
47
|
-
const testResults = raw.
|
|
48
|
-
!Array.isArray(raw.
|
|
49
|
-
? raw.
|
|
60
|
+
const testResults = raw.tests_reported && typeof raw.tests_reported === "object" &&
|
|
61
|
+
!Array.isArray(raw.tests_reported)
|
|
62
|
+
? raw.tests_reported
|
|
50
63
|
: undefined;
|
|
51
|
-
return
|
|
64
|
+
return makeExecutionReport({
|
|
52
65
|
files_changed: boundedStringArray(raw.files_changed, 200, 500),
|
|
53
|
-
|
|
66
|
+
tests_reported: testResults && typeof testResults.passed === "number" &&
|
|
54
67
|
Number.isFinite(testResults.passed)
|
|
55
68
|
? {
|
|
56
69
|
passed: nonNegativeInteger(testResults.passed),
|
|
@@ -58,14 +71,74 @@ export function parseExecutionEvidence(raw) {
|
|
|
58
71
|
skipped: nonNegativeInteger(testResults.skipped),
|
|
59
72
|
}
|
|
60
73
|
: null,
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
criterion_claims: parseCriterionClaims(raw.criterion_claims),
|
|
75
|
+
contract_item_claims: parseContractItemClaims(raw.contract_item_claims),
|
|
63
76
|
progress_estimate: typeof raw.progress_estimate === "number" &&
|
|
64
77
|
Number.isFinite(raw.progress_estimate)
|
|
65
78
|
? Math.max(0, Math.min(1, raw.progress_estimate))
|
|
66
79
|
: 0.0,
|
|
67
80
|
});
|
|
68
81
|
}
|
|
82
|
+
/** v3.8: The criterion ids the agent claims met this round. */
|
|
83
|
+
export function claimedMetCriteria(report) {
|
|
84
|
+
return (report?.criterion_claims ?? [])
|
|
85
|
+
.filter((claim) => claim.outcome === "met")
|
|
86
|
+
.map((claim) => claim.criterion_id);
|
|
87
|
+
}
|
|
88
|
+
/** v3.8: The criterion ids the agent declares still outstanding. */
|
|
89
|
+
export function claimedRemainingCriteria(report) {
|
|
90
|
+
return (report?.criterion_claims ?? [])
|
|
91
|
+
.filter((claim) => claim.outcome === "remaining")
|
|
92
|
+
.map((claim) => claim.criterion_id);
|
|
93
|
+
}
|
|
94
|
+
/** v3.8: LENIENT parse of advisory criterion claims — unknown or malformed
|
|
95
|
+
* entries are dropped, never a rejection (the contract item layer is the
|
|
96
|
+
* strict one). */
|
|
97
|
+
function parseCriterionClaims(raw) {
|
|
98
|
+
if (!Array.isArray(raw))
|
|
99
|
+
return [];
|
|
100
|
+
const claims = [];
|
|
101
|
+
for (const item of raw) {
|
|
102
|
+
if (claims.length >= 100)
|
|
103
|
+
break;
|
|
104
|
+
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
105
|
+
continue;
|
|
106
|
+
const entry = item;
|
|
107
|
+
if (typeof entry.criterion_id !== "string" || entry.criterion_id.trim().length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
if (entry.outcome !== "met" && entry.outcome !== "remaining")
|
|
110
|
+
continue;
|
|
111
|
+
claims.push({
|
|
112
|
+
criterion_id: entry.criterion_id.trim().slice(0, 500),
|
|
113
|
+
outcome: entry.outcome,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return claims;
|
|
117
|
+
}
|
|
118
|
+
/** v3.8: LENIENT parse of contract item claims. Referential validity is the
|
|
119
|
+
* strict pre-advance boundary in validateContractShape — this parser only
|
|
120
|
+
* normalizes what reaches the read model. */
|
|
121
|
+
function parseContractItemClaims(raw) {
|
|
122
|
+
if (!Array.isArray(raw))
|
|
123
|
+
return [];
|
|
124
|
+
const claims = [];
|
|
125
|
+
for (const item of raw) {
|
|
126
|
+
if (claims.length >= 20)
|
|
127
|
+
break;
|
|
128
|
+
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
129
|
+
continue;
|
|
130
|
+
const entry = item;
|
|
131
|
+
if (typeof entry.item_id !== "string")
|
|
132
|
+
continue;
|
|
133
|
+
if (entry.outcome !== "met" && entry.outcome !== "remaining")
|
|
134
|
+
continue;
|
|
135
|
+
claims.push({
|
|
136
|
+
item_id: entry.item_id.trim().slice(0, 64),
|
|
137
|
+
outcome: entry.outcome,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return claims;
|
|
141
|
+
}
|
|
69
142
|
/** Parse CriterionRevision[] from a raw JSON array. */
|
|
70
143
|
export function parseCriterionRevisions(raw) {
|
|
71
144
|
if (!Array.isArray(raw))
|
|
@@ -251,20 +324,295 @@ export function validateSubGoalUpdatesShape(raw) {
|
|
|
251
324
|
detail: `entry ${index} status must be one of in_progress | done | blocked | canceled`,
|
|
252
325
|
});
|
|
253
326
|
}
|
|
254
|
-
|
|
327
|
+
// v3.8.1: `note` is deliberately NOT validated. protocol.ts documents it
|
|
328
|
+
// as "lenient — never validated" free text, and `parseSubGoalUpdates`
|
|
329
|
+
// drops a non-string note. Rejecting the WHOLE submission over a
|
|
330
|
+
// reporting annotation contradicted that boundary: it was the only
|
|
331
|
+
// lenient field that hard-rejected a round.
|
|
332
|
+
});
|
|
333
|
+
return errors;
|
|
334
|
+
}
|
|
335
|
+
const SUBGOAL_ID_RE = /^sg-[a-f0-9]{8}$/;
|
|
336
|
+
/** v3.8: `scope` is the round's declared file boundary, so containment is a
|
|
337
|
+
* trust boundary like every other workspace path — a declaration that names
|
|
338
|
+
* `/etc` or `../outside` is rejected, not silently normalized into a string
|
|
339
|
+
* the drift check can never match. */
|
|
340
|
+
function validateContractScope(scope, checkScopeEntry, errors) {
|
|
341
|
+
if (scope === undefined)
|
|
342
|
+
return;
|
|
343
|
+
if (!Array.isArray(scope)) {
|
|
344
|
+
errors.push({
|
|
345
|
+
field: "round_contract",
|
|
346
|
+
reason: "invalid_scope",
|
|
347
|
+
detail: "scope must be an array of workspace-relative paths",
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (scope.length > CONTRACT_LIMITS.scope) {
|
|
352
|
+
errors.push({
|
|
353
|
+
field: "round_contract",
|
|
354
|
+
reason: "too_many_scope_entries",
|
|
355
|
+
detail: `scope may list at most ${CONTRACT_LIMITS.scope} entries (got ${scope.length})`,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
scope.forEach((entry, index) => {
|
|
359
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
255
360
|
errors.push({
|
|
256
|
-
field: "
|
|
257
|
-
reason: "
|
|
258
|
-
detail: `entry ${index}
|
|
361
|
+
field: "round_contract",
|
|
362
|
+
reason: "invalid_scope_entry",
|
|
363
|
+
detail: `scope entry ${index} must be a non-empty string`,
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const detail = checkScopeEntry(entry.trim());
|
|
368
|
+
if (detail) {
|
|
369
|
+
errors.push({
|
|
370
|
+
field: "round_contract",
|
|
371
|
+
reason: "scope_outside_workspace",
|
|
372
|
+
detail: `scope entry ${index} "${entry.trim()}" ${detail}`,
|
|
259
373
|
});
|
|
260
374
|
}
|
|
261
375
|
});
|
|
376
|
+
}
|
|
377
|
+
function validateContractItem(item, index, context, errors) {
|
|
378
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
379
|
+
errors.push({
|
|
380
|
+
field: "round_contract",
|
|
381
|
+
reason: "invalid_item",
|
|
382
|
+
detail: `item ${index} is not an object`,
|
|
383
|
+
});
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const entry = item;
|
|
387
|
+
if (typeof entry.description !== "string" || entry.description.trim().length === 0) {
|
|
388
|
+
errors.push({
|
|
389
|
+
field: "round_contract",
|
|
390
|
+
reason: "empty_item_description",
|
|
391
|
+
detail: `item ${index} needs a non-empty description`,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const verifyWith = entry.verify_with;
|
|
395
|
+
if (!Array.isArray(verifyWith) || verifyWith.length === 0) {
|
|
396
|
+
errors.push({
|
|
397
|
+
field: "round_contract",
|
|
398
|
+
reason: "no_verify_with",
|
|
399
|
+
detail: `item ${index} must bind at least one evidence command in verify_with`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
if (verifyWith.length > CONTRACT_LIMITS.verifyWith) {
|
|
404
|
+
errors.push({
|
|
405
|
+
field: "round_contract",
|
|
406
|
+
reason: "too_many_verify_with",
|
|
407
|
+
detail: `item ${index} verify_with may name at most ${CONTRACT_LIMITS.verifyWith} commands ` +
|
|
408
|
+
`(got ${verifyWith.length})`,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
for (const name of verifyWith) {
|
|
412
|
+
if (typeof name !== "string" || !context.isConfiguredCommand(name)) {
|
|
413
|
+
errors.push({
|
|
414
|
+
field: "round_contract",
|
|
415
|
+
reason: "unknown_command",
|
|
416
|
+
detail: `item ${index} verify_with names "${String(name)}", which is not a ` +
|
|
417
|
+
"configured, enabled, after-capable evidence command",
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const criterionRefs = entry.criterion_refs;
|
|
423
|
+
if (criterionRefs !== undefined) {
|
|
424
|
+
if (!Array.isArray(criterionRefs)) {
|
|
425
|
+
errors.push({
|
|
426
|
+
field: "round_contract",
|
|
427
|
+
reason: "invalid_criterion_refs",
|
|
428
|
+
detail: `item ${index} criterion_refs must be an array of strings`,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
if (criterionRefs.length > CONTRACT_LIMITS.criterionRefs) {
|
|
433
|
+
errors.push({
|
|
434
|
+
field: "round_contract",
|
|
435
|
+
reason: "too_many_criterion_refs",
|
|
436
|
+
detail: `item ${index} criterion_refs may list at most ` +
|
|
437
|
+
`${CONTRACT_LIMITS.criterionRefs} ids (got ${criterionRefs.length})`,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
criterionRefs.forEach((ref, refIndex) => {
|
|
441
|
+
if (typeof ref !== "string" || ref.trim().length === 0) {
|
|
442
|
+
errors.push({
|
|
443
|
+
field: "round_contract",
|
|
444
|
+
reason: "invalid_criterion_ref",
|
|
445
|
+
detail: `item ${index} criterion_refs entry ${refIndex} must be a non-empty string`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const subgoalRefs = entry.subgoal_refs;
|
|
452
|
+
if (subgoalRefs !== undefined) {
|
|
453
|
+
if (!Array.isArray(subgoalRefs)) {
|
|
454
|
+
errors.push({
|
|
455
|
+
field: "round_contract",
|
|
456
|
+
reason: "invalid_subgoal_refs",
|
|
457
|
+
detail: `item ${index} subgoal_refs must be an array of sg-XXXXXXXX ids`,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
if (subgoalRefs.length > CONTRACT_LIMITS.subgoalRefs) {
|
|
462
|
+
errors.push({
|
|
463
|
+
field: "round_contract",
|
|
464
|
+
reason: "too_many_subgoal_refs",
|
|
465
|
+
detail: `item ${index} subgoal_refs may list at most ` +
|
|
466
|
+
`${CONTRACT_LIMITS.subgoalRefs} ids (got ${subgoalRefs.length})`,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
const seen = new Set();
|
|
470
|
+
for (const ref of subgoalRefs) {
|
|
471
|
+
if (typeof ref !== "string" || !SUBGOAL_ID_RE.test(ref.trim())) {
|
|
472
|
+
errors.push({
|
|
473
|
+
field: "round_contract",
|
|
474
|
+
reason: "invalid_subgoal_ref",
|
|
475
|
+
detail: `item ${index} subgoal_refs entry "${String(ref)}" must be an sg-XXXXXXXX id`,
|
|
476
|
+
});
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const id = ref.trim();
|
|
480
|
+
if (seen.has(id)) {
|
|
481
|
+
errors.push({
|
|
482
|
+
field: "round_contract",
|
|
483
|
+
reason: "duplicate_subgoal_ref",
|
|
484
|
+
detail: `item ${index} subgoal_refs repeats ${id}`,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
seen.add(id);
|
|
488
|
+
if (context.knownSubGoalIds && !context.knownSubGoalIds.has(id)) {
|
|
489
|
+
errors.push({
|
|
490
|
+
field: "round_contract",
|
|
491
|
+
reason: "unknown_subgoal_ref",
|
|
492
|
+
detail: `item ${index} references ${id}, which is not a sub-goal of this loop`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/** v3.8: Strict STRUCTURAL boundary for the Round Contract declaration and
|
|
500
|
+
* the contract item claims — the same pre-advance contract as
|
|
501
|
+
* subgoal_updates: an error here is a payload defect, never a work-quality
|
|
502
|
+
* rejection (no session state, no gates, no rejection counters, retry with
|
|
503
|
+
* the same roundId as `contract_invalid`).
|
|
504
|
+
*
|
|
505
|
+
* Declaration strictness: items must exist and stay within CONTRACT_LIMITS;
|
|
506
|
+
* every item must bind at least one configured, enabled, after-capable
|
|
507
|
+
* command; scope entries must stay inside the workspace; criterion and
|
|
508
|
+
* sub-goal references must be well-formed, and sub-goal references must name
|
|
509
|
+
* a sub-goal that actually exists. The limits are enforced HERE rather than
|
|
510
|
+
* by silently truncating in the lenient parser — a declaration is a
|
|
511
|
+
* boundary, not a suggestion.
|
|
512
|
+
*
|
|
513
|
+
* Claim strictness: item ids must be well formed, unique, and — when the
|
|
514
|
+
* active contract is known — reference an item of the ACTIVE contract. */
|
|
515
|
+
export function validateContractShape(raw, context) {
|
|
516
|
+
const errors = [];
|
|
517
|
+
const contract = raw.round_contract;
|
|
518
|
+
if (contract !== undefined) {
|
|
519
|
+
if (typeof contract !== "object" || contract === null || Array.isArray(contract)) {
|
|
520
|
+
errors.push({
|
|
521
|
+
field: "round_contract",
|
|
522
|
+
reason: "not_object",
|
|
523
|
+
detail: "round_contract must be an object with an items array",
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
const obj = contract;
|
|
528
|
+
validateContractScope(obj.scope, context.checkScopeEntry, errors);
|
|
529
|
+
const items = obj.items;
|
|
530
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
531
|
+
errors.push({
|
|
532
|
+
field: "round_contract",
|
|
533
|
+
reason: "no_items",
|
|
534
|
+
detail: "a contract must declare at least one item (description + verify_with)",
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
else if (items.length > CONTRACT_LIMITS.items) {
|
|
538
|
+
errors.push({
|
|
539
|
+
field: "round_contract",
|
|
540
|
+
reason: "too_many_items",
|
|
541
|
+
detail: `a contract may declare at most ${CONTRACT_LIMITS.items} items (got ${items.length})`,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
items.forEach((item, index) => {
|
|
546
|
+
validateContractItem(item, index, context, errors);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const report = raw.execution_report;
|
|
552
|
+
if (typeof report === "object" && report !== null && !Array.isArray(report)) {
|
|
553
|
+
const claims = report.contract_item_claims;
|
|
554
|
+
if (claims !== undefined) {
|
|
555
|
+
if (!Array.isArray(claims)) {
|
|
556
|
+
errors.push({
|
|
557
|
+
field: "execution_report.contract_item_claims",
|
|
558
|
+
reason: "not_array",
|
|
559
|
+
detail: "contract_item_claims must be an array of { item_id, outcome } entries",
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
const seen = new Set();
|
|
564
|
+
claims.forEach((claim, index) => {
|
|
565
|
+
if (typeof claim !== "object" || claim === null || Array.isArray(claim)) {
|
|
566
|
+
errors.push({
|
|
567
|
+
field: "execution_report.contract_item_claims",
|
|
568
|
+
reason: "invalid_entry",
|
|
569
|
+
detail: `entry ${index} is not an object`,
|
|
570
|
+
});
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const entry = claim;
|
|
574
|
+
const id = typeof entry.item_id === "string" ? entry.item_id.trim() : "";
|
|
575
|
+
if (!/^rci-[a-f0-9]{8}$/.test(id)) {
|
|
576
|
+
errors.push({
|
|
577
|
+
field: "execution_report.contract_item_claims",
|
|
578
|
+
reason: "invalid_item_id",
|
|
579
|
+
detail: `entry ${index} must reference an rci-XXXXXXXX contract item id`,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
if (seen.has(id)) {
|
|
584
|
+
errors.push({
|
|
585
|
+
field: "execution_report.contract_item_claims",
|
|
586
|
+
reason: "duplicate_item_id",
|
|
587
|
+
detail: `entry ${index} repeats item id ${id}`,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
seen.add(id);
|
|
591
|
+
if (context.activeItemIds && !context.activeItemIds.has(id)) {
|
|
592
|
+
errors.push({
|
|
593
|
+
field: "execution_report.contract_item_claims",
|
|
594
|
+
reason: "unknown_item_id",
|
|
595
|
+
detail: `entry ${index} references ${id}, which is not an item of the ACTIVE contract`,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (entry.outcome !== "met" && entry.outcome !== "remaining") {
|
|
600
|
+
errors.push({
|
|
601
|
+
field: "execution_report.contract_item_claims",
|
|
602
|
+
reason: "invalid_outcome",
|
|
603
|
+
detail: `entry ${index} outcome must be "met" or "remaining"`,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
262
610
|
return errors;
|
|
263
611
|
}
|
|
264
612
|
/** Build a SelfEvaluation from a parsed JSON object.
|
|
265
613
|
* Lenient parsing: missing optional fields get sensible defaults. */
|
|
266
614
|
export function buildSelfEvaluation(raw) {
|
|
267
|
-
const
|
|
615
|
+
const executionReport = parseExecutionReport(raw.execution_report);
|
|
268
616
|
const retractedConstraints = boundedStringArray(raw.retracted_constraints, 50, 500);
|
|
269
617
|
const revisedCriteria = parseCriterionRevisions(raw.revised_success_criteria);
|
|
270
618
|
const wrongAssumptions = boundedStringArray(raw.wrong_assumptions, 50, 500);
|
|
@@ -278,15 +626,14 @@ export function buildSelfEvaluation(raw) {
|
|
|
278
626
|
should_continue: typeof raw.should_continue === "boolean" ? raw.should_continue : true,
|
|
279
627
|
discovered_constraints: boundedStringArray(raw.discovered_constraints, 50, 500),
|
|
280
628
|
objective_refinement: boundedString(raw.objective_refinement, 1000) ?? "",
|
|
281
|
-
emerged_subtasks:
|
|
282
|
-
|
|
629
|
+
emerged_subtasks: boundedEmergedSubtasks(raw.emerged_subtasks),
|
|
630
|
+
execution_report: executionReport,
|
|
283
631
|
retracted_constraints: retractedConstraints,
|
|
284
632
|
revised_success_criteria: revisedCriteria,
|
|
285
633
|
wrong_assumptions: wrongAssumptions,
|
|
286
634
|
worker_results: workerResults,
|
|
287
635
|
compression_checkpoint: typeof raw.compression_checkpoint === "boolean" ? raw.compression_checkpoint : false,
|
|
288
636
|
checkpoint_label: boundedString(raw.checkpoint_label, 200) ?? "",
|
|
289
|
-
next_action: boundedString(raw.next_action, 500),
|
|
290
637
|
subgoal_updates: parseSubGoalUpdates(raw.subgoal_updates),
|
|
291
638
|
stop_reason: raw.stop_reason === "gave_up" || raw.stop_reason === "blocked" || raw.stop_reason === "needs_human_input"
|
|
292
639
|
? raw.stop_reason
|
|
@@ -303,16 +650,26 @@ export function buildSelfEvaluation(raw) {
|
|
|
303
650
|
no_change_reason: typeof raw.no_change_reason === "string" && raw.no_change_reason.trim().length > 0
|
|
304
651
|
? raw.no_change_reason.slice(0, 200)
|
|
305
652
|
: undefined,
|
|
306
|
-
drift_clarification: boundedString(raw.drift_clarification, 1000),
|
|
307
653
|
prompt_requests: parsePromptRequests(raw.prompt_requests),
|
|
308
654
|
round_contract: parseRoundContract(raw.round_contract),
|
|
309
655
|
});
|
|
310
656
|
}
|
|
311
|
-
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
|
|
657
|
+
/** v3.8: The ONE set of contract-declaration limits. `validateContractShape`
|
|
658
|
+
* (the strict boundary) rejects a declaration over these; `parseRoundContract`
|
|
659
|
+
* (the lenient path, reached only after the strict boundary passed) truncates
|
|
660
|
+
* to the same numbers. One source, so the two can never drift. */
|
|
661
|
+
export const CONTRACT_LIMITS = {
|
|
662
|
+
items: 20,
|
|
663
|
+
scope: 50,
|
|
664
|
+
criterionRefs: 20,
|
|
665
|
+
subgoalRefs: 20,
|
|
666
|
+
verifyWith: 20,
|
|
667
|
+
};
|
|
668
|
+
/** Parse a Round Contract PROPOSAL from raw JSON input. Lenient on shapes
|
|
669
|
+
* the strict declaration boundary already rejected: strings are trimmed and
|
|
670
|
+
* capped, non-string array entries are dropped. Item ORDER and COUNT are
|
|
671
|
+
* preserved — the derived rci- ids depend on them. Returns undefined only
|
|
672
|
+
* when the raw value is absent or not an object. */
|
|
316
673
|
export function parseRoundContract(raw) {
|
|
317
674
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
318
675
|
return undefined;
|
|
@@ -324,13 +681,25 @@ export function parseRoundContract(raw) {
|
|
|
324
681
|
.filter(Boolean)
|
|
325
682
|
.slice(0, cap)
|
|
326
683
|
: [];
|
|
684
|
+
const items = Array.isArray(obj.items)
|
|
685
|
+
? obj.items
|
|
686
|
+
.slice(0, CONTRACT_LIMITS.items)
|
|
687
|
+
.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item))
|
|
688
|
+
.map((item) => ({
|
|
689
|
+
description: typeof item.description === "string"
|
|
690
|
+
? item.description.trim().slice(0, 200)
|
|
691
|
+
: "",
|
|
692
|
+
criterion_refs: capStrings(item.criterion_refs, 200, CONTRACT_LIMITS.criterionRefs),
|
|
693
|
+
subgoal_refs: capStrings(item.subgoal_refs, 64, CONTRACT_LIMITS.subgoalRefs),
|
|
694
|
+
verify_with: capStrings(item.verify_with, 200, CONTRACT_LIMITS.verifyWith),
|
|
695
|
+
}))
|
|
696
|
+
: [];
|
|
327
697
|
return {
|
|
328
698
|
work_item: typeof obj.work_item === "string" && obj.work_item.trim().length > 0
|
|
329
699
|
? obj.work_item.trim().slice(0, 200)
|
|
330
700
|
: undefined,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
scope: capStrings(obj.scope, 500, 50),
|
|
701
|
+
scope: capStrings(obj.scope, 500, CONTRACT_LIMITS.scope),
|
|
702
|
+
items,
|
|
334
703
|
};
|
|
335
704
|
}
|
|
336
705
|
/** Parse a PromptRequests object from raw JSON input.
|