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.
Files changed (144) hide show
  1. package/README.md +171 -169
  2. package/dist/audit.d.ts +53 -1
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js +106 -22
  5. package/dist/audit.js.map +1 -1
  6. package/dist/canonical-state.d.ts +52 -67
  7. package/dist/canonical-state.d.ts.map +1 -1
  8. package/dist/canonical-state.js +134 -147
  9. package/dist/canonical-state.js.map +1 -1
  10. package/dist/cli.d.ts.map +1 -1
  11. package/dist/cli.js +103 -4
  12. package/dist/cli.js.map +1 -1
  13. package/dist/cognitive-facts.d.ts +13 -0
  14. package/dist/cognitive-facts.d.ts.map +1 -1
  15. package/dist/cognitive-facts.js +28 -13
  16. package/dist/cognitive-facts.js.map +1 -1
  17. package/dist/committed-round.d.ts +103 -21
  18. package/dist/committed-round.d.ts.map +1 -1
  19. package/dist/committed-round.js +188 -45
  20. package/dist/committed-round.js.map +1 -1
  21. package/dist/contract-items.d.ts +55 -0
  22. package/dist/contract-items.d.ts.map +1 -0
  23. package/dist/contract-items.js +180 -0
  24. package/dist/contract-items.js.map +1 -0
  25. package/dist/enforcement-gate.d.ts +20 -2
  26. package/dist/enforcement-gate.d.ts.map +1 -1
  27. package/dist/enforcement-gate.js +197 -386
  28. package/dist/enforcement-gate.js.map +1 -1
  29. package/dist/engine.d.ts +1 -1
  30. package/dist/engine.d.ts.map +1 -1
  31. package/dist/engine.js +53 -38
  32. package/dist/engine.js.map +1 -1
  33. package/dist/evidence-claims.d.ts +4 -4
  34. package/dist/evidence-claims.d.ts.map +1 -1
  35. package/dist/evidence-claims.js +16 -14
  36. package/dist/evidence-claims.js.map +1 -1
  37. package/dist/evidence-provider.d.ts +85 -89
  38. package/dist/evidence-provider.d.ts.map +1 -1
  39. package/dist/evidence-provider.js +328 -184
  40. package/dist/evidence-provider.js.map +1 -1
  41. package/dist/explain.d.ts +76 -0
  42. package/dist/explain.d.ts.map +1 -0
  43. package/dist/explain.js +127 -0
  44. package/dist/explain.js.map +1 -0
  45. package/dist/generate-schema.js +152 -16
  46. package/dist/generate-schema.js.map +1 -1
  47. package/dist/index.d.ts +6 -6
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -5
  50. package/dist/index.js.map +1 -1
  51. package/dist/loop-compiler.d.ts +40 -53
  52. package/dist/loop-compiler.d.ts.map +1 -1
  53. package/dist/loop-compiler.js +229 -553
  54. package/dist/loop-compiler.js.map +1 -1
  55. package/dist/loop-extras-parser.d.ts +0 -1
  56. package/dist/loop-extras-parser.d.ts.map +1 -1
  57. package/dist/loop-extras-parser.js +0 -4
  58. package/dist/loop-extras-parser.js.map +1 -1
  59. package/dist/loop-projection.d.ts.map +1 -1
  60. package/dist/loop-projection.js +3 -1
  61. package/dist/loop-projection.js.map +1 -1
  62. package/dist/mcp/round-lifecycle.d.ts +30 -21
  63. package/dist/mcp/round-lifecycle.d.ts.map +1 -1
  64. package/dist/mcp/round-lifecycle.js +47 -103
  65. package/dist/mcp/round-lifecycle.js.map +1 -1
  66. package/dist/mcp/server.d.ts.map +1 -1
  67. package/dist/mcp/server.js +51 -7
  68. package/dist/mcp/server.js.map +1 -1
  69. package/dist/mcp/session.d.ts +39 -23
  70. package/dist/mcp/session.d.ts.map +1 -1
  71. package/dist/mcp/session.js +148 -163
  72. package/dist/mcp/session.js.map +1 -1
  73. package/dist/mcp/submission-boundary.d.ts +89 -0
  74. package/dist/mcp/submission-boundary.d.ts.map +1 -0
  75. package/dist/mcp/submission-boundary.js +203 -0
  76. package/dist/mcp/submission-boundary.js.map +1 -0
  77. package/dist/mcp/tools.d.ts.map +1 -1
  78. package/dist/mcp/tools.js +249 -128
  79. package/dist/mcp/tools.js.map +1 -1
  80. package/dist/policy-metrics.js +2 -2
  81. package/dist/policy-metrics.js.map +1 -1
  82. package/dist/policy.d.ts +55 -93
  83. package/dist/policy.d.ts.map +1 -1
  84. package/dist/policy.js +149 -38
  85. package/dist/policy.js.map +1 -1
  86. package/dist/prompt-assembler.d.ts +1 -32
  87. package/dist/prompt-assembler.d.ts.map +1 -1
  88. package/dist/prompt-assembler.js +282 -435
  89. package/dist/prompt-assembler.js.map +1 -1
  90. package/dist/prompt-policy.d.ts +1 -4
  91. package/dist/prompt-policy.d.ts.map +1 -1
  92. package/dist/prompt-policy.js +6 -7
  93. package/dist/prompt-policy.js.map +1 -1
  94. package/dist/protocol.d.ts +365 -142
  95. package/dist/protocol.d.ts.map +1 -1
  96. package/dist/protocol.js +21 -66
  97. package/dist/protocol.js.map +1 -1
  98. package/dist/replay.js +2 -2
  99. package/dist/replay.js.map +1 -1
  100. package/dist/round-contract.d.ts +69 -57
  101. package/dist/round-contract.d.ts.map +1 -1
  102. package/dist/round-contract.js +104 -64
  103. package/dist/round-contract.js.map +1 -1
  104. package/dist/round-coordinator.d.ts +36 -16
  105. package/dist/round-coordinator.d.ts.map +1 -1
  106. package/dist/round-coordinator.js +166 -102
  107. package/dist/round-coordinator.js.map +1 -1
  108. package/dist/round-driver.d.ts +3 -5
  109. package/dist/round-driver.d.ts.map +1 -1
  110. package/dist/round-driver.js +0 -1
  111. package/dist/round-driver.js.map +1 -1
  112. package/dist/round-facts.d.ts +96 -0
  113. package/dist/round-facts.d.ts.map +1 -0
  114. package/dist/round-facts.js +216 -0
  115. package/dist/round-facts.js.map +1 -0
  116. package/dist/round-transaction.d.ts +19 -9
  117. package/dist/round-transaction.d.ts.map +1 -1
  118. package/dist/round-transaction.js +90 -22
  119. package/dist/round-transaction.js.map +1 -1
  120. package/dist/self-eval.d.ts +87 -9
  121. package/dist/self-eval.d.ts.map +1 -1
  122. package/dist/self-eval.js +396 -27
  123. package/dist/self-eval.js.map +1 -1
  124. package/dist/subgoal-state.d.ts +77 -0
  125. package/dist/subgoal-state.d.ts.map +1 -0
  126. package/dist/subgoal-state.js +198 -0
  127. package/dist/subgoal-state.js.map +1 -0
  128. package/dist/token-utils.d.ts +52 -19
  129. package/dist/token-utils.d.ts.map +1 -1
  130. package/dist/token-utils.js +83 -72
  131. package/dist/token-utils.js.map +1 -1
  132. package/dist/verification-gate.d.ts +14 -31
  133. package/dist/verification-gate.d.ts.map +1 -1
  134. package/dist/verification-gate.js +178 -496
  135. package/dist/verification-gate.js.map +1 -1
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/workspace.d.ts +9 -0
  139. package/dist/workspace.d.ts.map +1 -1
  140. package/dist/workspace.js +17 -0
  141. package/dist/workspace.js.map +1 -1
  142. package/loop_policy.json +6 -30
  143. package/package.json +1 -1
  144. package/skills/perception/SKILL.md +243 -79
@@ -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 ExecutionEvidence, type PromptRequests, type RoundContract, type RoundOutcome, type SelfEvaluation, type SubGoalUpdate } from "./protocol.js";
5
- /** Parse ExecutionEvidence from a raw JSON object. */
6
- export declare function parseExecutionEvidence(raw: Record<string, unknown> | undefined | null): ExecutionEvidence | undefined;
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
- /** Parse a RoundContract object from raw JSON input. Lenient: non-string
72
- * entries are dropped, strings are trimmed and capped. An object that IS
73
- * present is returned even with empty arrays — an empty contract is a
74
- * real declaration the round_underspecified check must see. Returns
75
- * undefined only when the raw value is absent or not an object. */
76
- export declare function parseRoundContract(raw: unknown): RoundContract | undefined;
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,
@@ -1 +1 @@
1
- {"version":3,"file":"self-eval.d.ts","sourceRoot":"","sources":["../src/self-eval.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AA8CvB,sDAAsD;AACtD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GAC9C,iBAAiB,GAAG,SAAS,CAuB/B;AAED,uDAAuD;AACvD,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,GACX,iBAAiB,EAAE,CAYrB;AAED;;;;8EAI8E;AAC9E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,EAAE,CAqBjE;AAED;;;;2DAI2D;AAC3D,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,GACX,OAAO,eAAe,EAAE,YAAY,EAAE,CAyBxC;AAED;;;0CAG0C;AAC1C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,GAAG,YAAY,CAMvE;AAED;0EAC0E;AAC1E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAElE;AAED;;;;;;;;gDAQgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3E;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,oBAAoB,CAsBtB;AAED;;;;;;;;;;;yBAWyB;AACzB,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAmE1D;AAED;sEACsE;AACtE,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,cAAc,CAqDhB;AAED;;;;oEAIoE;AACpE,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,GACX,aAAa,GAAG,SAAS,CAoB3B;AAED;;;;;;;iEAOiE;AACjE,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,GACX,cAAc,GAAG,SAAS,CAW5B"}
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 { makeExecutionEvidence, makeSelfEvaluation, } from "./protocol.js";
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 ExecutionEvidence from a raw JSON object. */
44
- export function parseExecutionEvidence(raw) {
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.test_results && typeof raw.test_results === "object" &&
48
- !Array.isArray(raw.test_results)
49
- ? raw.test_results
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 makeExecutionEvidence({
64
+ return makeExecutionReport({
52
65
  files_changed: boundedStringArray(raw.files_changed, 200, 500),
53
- test_results: testResults && typeof testResults.passed === "number" &&
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
- success_criteria_met: boundedStringArray(raw.success_criteria_met, 100, 500),
62
- success_criteria_remaining: boundedStringArray(raw.success_criteria_remaining, 100, 500),
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
- if (u.note !== undefined && typeof u.note !== "string") {
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: "subgoal_updates",
257
- reason: "invalid_note",
258
- detail: `entry ${index} note must be a string`,
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 executionEvidence = parseExecutionEvidence(raw.execution_evidence);
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: boundedStringArray(raw.emerged_subtasks, 50, 500),
282
- execution_evidence: executionEvidence,
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
- /** Parse a RoundContract object from raw JSON input. Lenient: non-string
312
- * entries are dropped, strings are trimmed and capped. An object that IS
313
- * present is returned even with empty arrays — an empty contract is a
314
- * real declaration the round_underspecified check must see. Returns
315
- * undefined only when the raw value is absent or not an object. */
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
- done_when: capStrings(obj.done_when, 200, 20),
332
- verification_plan: capStrings(obj.verification_plan, 200, 20),
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.