loopforge 3.9.4 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -112
- package/dist/audit.d.ts +73 -15
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +131 -61
- package/dist/audit.js.map +1 -1
- package/dist/canonical-state.d.ts +114 -40
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +276 -182
- package/dist/canonical-state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -12
- package/dist/cli.js.map +1 -1
- package/dist/cognitive-governance.d.ts +0 -2
- package/dist/cognitive-governance.d.ts.map +1 -1
- package/dist/cognitive-governance.js +0 -4
- package/dist/cognitive-governance.js.map +1 -1
- package/dist/committed-round.d.ts +97 -156
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +223 -419
- package/dist/committed-round.js.map +1 -1
- package/dist/constraint-lifecycle.d.ts +73 -0
- package/dist/constraint-lifecycle.d.ts.map +1 -0
- package/dist/constraint-lifecycle.js +174 -0
- package/dist/constraint-lifecycle.js.map +1 -0
- package/dist/context-boundary.d.ts +83 -0
- package/dist/context-boundary.d.ts.map +1 -0
- package/dist/context-boundary.js +101 -0
- package/dist/context-boundary.js.map +1 -0
- package/dist/contract-items.d.ts +12 -8
- package/dist/contract-items.d.ts.map +1 -1
- package/dist/contract-items.js +31 -26
- package/dist/contract-items.js.map +1 -1
- package/dist/contract-requirement.d.ts +47 -0
- package/dist/contract-requirement.d.ts.map +1 -0
- package/dist/contract-requirement.js +51 -0
- package/dist/contract-requirement.js.map +1 -0
- package/dist/criterion-status.d.ts +35 -0
- package/dist/criterion-status.d.ts.map +1 -0
- package/dist/criterion-status.js +89 -0
- package/dist/criterion-status.js.map +1 -0
- package/dist/enforcement-gate.d.ts +26 -38
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +196 -319
- package/dist/enforcement-gate.js.map +1 -1
- package/dist/engine.d.ts +9 -103
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -897
- package/dist/engine.js.map +1 -1
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-claims.d.ts +33 -31
- package/dist/evidence-claims.d.ts.map +1 -1
- package/dist/evidence-claims.js +58 -72
- package/dist/evidence-claims.js.map +1 -1
- package/dist/evidence-posture-view.d.ts +19 -0
- package/dist/evidence-posture-view.d.ts.map +1 -0
- package/dist/evidence-posture-view.js +116 -0
- package/dist/evidence-posture-view.js.map +1 -0
- package/dist/evidence-posture.d.ts +73 -0
- package/dist/evidence-posture.d.ts.map +1 -0
- package/dist/evidence-posture.js +281 -0
- package/dist/evidence-posture.js.map +1 -0
- package/dist/evidence-provider.d.ts +55 -37
- package/dist/evidence-provider.d.ts.map +1 -1
- package/dist/evidence-provider.js +182 -332
- package/dist/evidence-provider.js.map +1 -1
- package/dist/explain.d.ts +166 -27
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +372 -58
- package/dist/explain.js.map +1 -1
- package/dist/field-accounting.d.ts.map +1 -1
- package/dist/field-accounting.js +3 -22
- package/dist/field-accounting.js.map +1 -1
- package/dist/generate-schema.js +13 -8
- package/dist/generate-schema.js.map +1 -1
- package/dist/git-boundary.d.ts +128 -0
- package/dist/git-boundary.d.ts.map +1 -0
- package/dist/git-boundary.js +625 -0
- package/dist/git-boundary.js.map +1 -0
- package/dist/git-facts.d.ts +22 -0
- package/dist/git-facts.d.ts.map +1 -0
- package/dist/git-facts.js +29 -0
- package/dist/git-facts.js.map +1 -0
- package/dist/git-motion.d.ts +37 -0
- package/dist/git-motion.d.ts.map +1 -0
- package/dist/git-motion.js +59 -0
- package/dist/git-motion.js.map +1 -0
- package/dist/history.d.ts +44 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +77 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +16 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +80 -14
- package/dist/init.js.map +1 -1
- package/dist/loop-compiler.d.ts +39 -23
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +368 -565
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-store.d.ts +6 -5
- package/dist/loop-store.d.ts.map +1 -1
- package/dist/loop-store.js +153 -35
- package/dist/loop-store.js.map +1 -1
- package/dist/mcp/round-lifecycle.d.ts +255 -81
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +1552 -539
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +42 -18
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +81 -39
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +956 -282
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/submission-boundary.d.ts +23 -31
- package/dist/mcp/submission-boundary.d.ts.map +1 -1
- package/dist/mcp/submission-boundary.js +242 -92
- package/dist/mcp/submission-boundary.js.map +1 -1
- package/dist/mcp/submission-handle.d.ts +66 -0
- package/dist/mcp/submission-handle.d.ts.map +1 -0
- package/dist/mcp/submission-handle.js +173 -0
- package/dist/mcp/submission-handle.js.map +1 -0
- package/dist/mcp/tools.d.ts +17 -7
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +707 -244
- package/dist/mcp/tools.js.map +1 -1
- package/dist/open-question.d.ts +7 -0
- package/dist/open-question.d.ts.map +1 -0
- package/dist/open-question.js +14 -0
- package/dist/open-question.js.map +1 -0
- package/dist/policy-metrics.d.ts +31 -21
- package/dist/policy-metrics.d.ts.map +1 -1
- package/dist/policy-metrics.js +117 -64
- package/dist/policy-metrics.js.map +1 -1
- package/dist/policy.d.ts +11 -39
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +12 -12
- package/dist/policy.js.map +1 -1
- package/dist/progress.d.ts +45 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +136 -0
- package/dist/progress.js.map +1 -0
- package/dist/prompt-assembler.d.ts +20 -6
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +475 -479
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-coverage.d.ts +0 -6
- package/dist/prompt-coverage.d.ts.map +1 -1
- package/dist/prompt-coverage.js +4 -8
- package/dist/prompt-coverage.js.map +1 -1
- package/dist/prompt-freshness.d.ts +6 -17
- package/dist/prompt-freshness.d.ts.map +1 -1
- package/dist/prompt-freshness.js +27 -22
- package/dist/prompt-freshness.js.map +1 -1
- package/dist/prompt-policy.d.ts +14 -7
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +21 -5
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +843 -424
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +73 -102
- package/dist/protocol.js.map +1 -1
- package/dist/recovery-guidance.d.ts +13 -0
- package/dist/recovery-guidance.d.ts.map +1 -0
- package/dist/recovery-guidance.js +15 -0
- package/dist/recovery-guidance.js.map +1 -0
- package/dist/recovery.d.ts +68 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +234 -0
- package/dist/recovery.js.map +1 -0
- package/dist/replay.d.ts +42 -8
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +311 -84
- package/dist/replay.js.map +1 -1
- package/dist/result-facts.d.ts +45 -0
- package/dist/result-facts.d.ts.map +1 -0
- package/dist/result-facts.js +86 -0
- package/dist/result-facts.js.map +1 -0
- package/dist/round-contract.d.ts +38 -8
- package/dist/round-contract.d.ts.map +1 -1
- package/dist/round-contract.js +71 -31
- package/dist/round-contract.js.map +1 -1
- package/dist/round-coordinator.d.ts +41 -89
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +250 -267
- package/dist/round-coordinator.js.map +1 -1
- package/dist/round-driver.d.ts +56 -16
- package/dist/round-driver.d.ts.map +1 -1
- package/dist/round-driver.js +225 -39
- package/dist/round-driver.js.map +1 -1
- package/dist/round-facts.d.ts +33 -52
- package/dist/round-facts.d.ts.map +1 -1
- package/dist/round-facts.js +51 -160
- package/dist/round-facts.js.map +1 -1
- package/dist/round-guidance.d.ts +56 -0
- package/dist/round-guidance.d.ts.map +1 -0
- package/dist/round-guidance.js +161 -0
- package/dist/round-guidance.js.map +1 -0
- package/dist/round-outcome.d.ts +21 -0
- package/dist/round-outcome.d.ts.map +1 -0
- package/dist/round-outcome.js +31 -0
- package/dist/round-outcome.js.map +1 -0
- package/dist/round-transaction.d.ts +168 -21
- package/dist/round-transaction.d.ts.map +1 -1
- package/dist/round-transaction.js +837 -38
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +37 -61
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +413 -392
- package/dist/self-eval.js.map +1 -1
- package/dist/storage.d.ts +16 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +54 -4
- package/dist/storage.js.map +1 -1
- package/dist/subgoal-state.d.ts +2 -3
- package/dist/subgoal-state.d.ts.map +1 -1
- package/dist/subgoal-state.js +2 -3
- package/dist/subgoal-state.js.map +1 -1
- package/dist/submission-guidance.d.ts +29 -21
- package/dist/submission-guidance.d.ts.map +1 -1
- package/dist/submission-guidance.js +308 -260
- package/dist/submission-guidance.js.map +1 -1
- package/dist/submission-receipt.d.ts +16 -8
- package/dist/submission-receipt.d.ts.map +1 -1
- package/dist/submission-receipt.js +145 -51
- package/dist/submission-receipt.js.map +1 -1
- package/dist/token-utils.d.ts +28 -7
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +27 -11
- package/dist/token-utils.js.map +1 -1
- package/dist/transaction-identity.d.ts +9 -0
- package/dist/transaction-identity.d.ts.map +1 -0
- package/dist/transaction-identity.js +69 -0
- package/dist/transaction-identity.js.map +1 -0
- package/dist/verification-actions.d.ts.map +1 -1
- package/dist/verification-actions.js +10 -11
- package/dist/verification-actions.js.map +1 -1
- package/dist/verification-gate.d.ts +23 -47
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +202 -423
- package/dist/verification-gate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +187 -0
- package/dist/working-memory.d.ts.map +1 -0
- package/dist/working-memory.js +767 -0
- package/dist/working-memory.js.map +1 -0
- package/dist/workspace-checkpoint.d.ts +88 -0
- package/dist/workspace-checkpoint.d.ts.map +1 -0
- package/dist/workspace-checkpoint.js +326 -0
- package/dist/workspace-checkpoint.js.map +1 -0
- package/dist/workspace-restore.d.ts +68 -0
- package/dist/workspace-restore.d.ts.map +1 -0
- package/dist/workspace-restore.js +604 -0
- package/dist/workspace-restore.js.map +1 -0
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +0 -17
- package/dist/workspace.js.map +1 -1
- package/loop_policy.json +4 -18
- package/package.json +68 -68
- package/skills/forge/SKILL.md +212 -0
- package/skills/forge/references/evidence-and-contracts.md +204 -0
- package/skills/forge/references/recovery-and-views.md +188 -0
- package/dist/cognitive-facts.d.ts +0 -30
- package/dist/cognitive-facts.d.ts.map +0 -1
- package/dist/cognitive-facts.js +0 -113
- package/dist/cognitive-facts.js.map +0 -1
- package/dist/loop-extras-parser.d.ts +0 -81
- package/dist/loop-extras-parser.d.ts.map +0 -1
- package/dist/loop-extras-parser.js +0 -213
- package/dist/loop-extras-parser.js.map +0 -1
- package/dist/loop-projection.d.ts +0 -6
- package/dist/loop-projection.d.ts.map +0 -1
- package/dist/loop-projection.js +0 -28
- package/dist/loop-projection.js.map +0 -1
- package/skills/perception/SKILL.md +0 -389
package/dist/committed-round.js
CHANGED
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
* place that interprets their transaction envelope or the compiler's merged
|
|
5
5
|
* lineage representation. Consumers receive one stable, read-only view.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { observationVersionIssue } from "./evidence-provider.js";
|
|
8
|
+
import { promptContextPairIssue } from "./context-boundary.js";
|
|
9
|
+
import { PROMPT_ARTIFACT_SCHEMA_VERSION, isPromptContextMode, isPromptContextReason, makeLoopRoundResult, } from "./protocol.js";
|
|
10
|
+
import { ROUND_TRANSACTION_SCHEMA_VERSION, deriveRoundObservationDelta, isProcessResult, parseRoundTransactionSnapshot, rootBranchId, transactionSchemaVersionOf, } from "./round-transaction.js";
|
|
11
|
+
import { effectiveOutcome, parseRoundContract, } from "./self-eval.js";
|
|
10
12
|
import { entryRound, isRecord } from "./token-utils.js";
|
|
13
|
+
import { transactionIdentityIssue } from "./transaction-identity.js";
|
|
11
14
|
function committedAction(value) {
|
|
12
|
-
return value === "continue" || value === "stop" ||
|
|
15
|
+
return value === "continue" || value === "stop" ||
|
|
16
|
+
value === "recovery" || value === "backtrack"
|
|
13
17
|
? value
|
|
14
18
|
: null;
|
|
15
19
|
}
|
|
@@ -19,56 +23,11 @@ function snapshots(value) {
|
|
|
19
23
|
function flags(value) {
|
|
20
24
|
return Array.isArray(value) ? value : [];
|
|
21
25
|
}
|
|
22
|
-
function roundOutcome(value) {
|
|
23
|
-
return value === "success" || value === "partial" ||
|
|
24
|
-
value === "failed" || value === "blocked"
|
|
25
|
-
? value
|
|
26
|
-
: null;
|
|
27
|
-
}
|
|
28
26
|
function strings(value) {
|
|
29
27
|
return Array.isArray(value)
|
|
30
28
|
? value.filter((item) => typeof item === "string")
|
|
31
29
|
: [];
|
|
32
30
|
}
|
|
33
|
-
function numbers(value) {
|
|
34
|
-
return Array.isArray(value)
|
|
35
|
-
? value.filter((item) => typeof item === "number")
|
|
36
|
-
: [];
|
|
37
|
-
}
|
|
38
|
-
function stringMap(value) {
|
|
39
|
-
if (!isRecord(value))
|
|
40
|
-
return {};
|
|
41
|
-
const out = {};
|
|
42
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
43
|
-
if (typeof entry === "string")
|
|
44
|
-
out[key] = entry;
|
|
45
|
-
}
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
/** v3.8.1: decode a rollback directive's facts from either carrier —
|
|
49
|
-
* `result.*` on a durable :feedback entry, `lineage.*` on a hydrated merged
|
|
50
|
-
* entry. Returns null when no restore point is recorded, which is also the
|
|
51
|
-
* signal that the record cannot serve as recovery guidance. */
|
|
52
|
-
export function decodeBacktrackRecord(source) {
|
|
53
|
-
const value = isRecord(source) ? source : {};
|
|
54
|
-
const target = typeof value.backtrackTarget === "number" ? value.backtrackTarget : null;
|
|
55
|
-
if (target === null)
|
|
56
|
-
return null;
|
|
57
|
-
return {
|
|
58
|
-
target,
|
|
59
|
-
triggerRule: typeof value.backtrackTriggerRule === "string"
|
|
60
|
-
? value.backtrackTriggerRule
|
|
61
|
-
: "",
|
|
62
|
-
failedRounds: numbers(value.backtrackFailedRounds),
|
|
63
|
-
approaches: strings(value.backtrackApproaches),
|
|
64
|
-
wrongAssumptions: strings(value.backtrackWrongAssumptions),
|
|
65
|
-
skippedFiles: strings(value.backtrackSkippedFiles),
|
|
66
|
-
skippedFingerprints: stringMap(value.backtrackSkippedFingerprints),
|
|
67
|
-
targetGitHead: typeof value.backtrackTargetGitHead === "string"
|
|
68
|
-
? value.backtrackTargetGitHead
|
|
69
|
-
: undefined,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
31
|
/** Normalize a raw subgoal_updates value into typed entries. Decode stays
|
|
73
32
|
* tolerant of any shape (parser caps apply at ingestion; this is the
|
|
74
33
|
* read-model view of what was committed). */
|
|
@@ -114,7 +73,7 @@ function evidenceIncomplete(after, deltas) {
|
|
|
114
73
|
* feedback entry and the engine's hydrated merged entry call it, so there is
|
|
115
74
|
* no second evaluation/history interpretation. */
|
|
116
75
|
function viewFromSnapshot(input) {
|
|
117
|
-
const { snapshot, result
|
|
76
|
+
const { snapshot, result } = input;
|
|
118
77
|
if (snapshot.phase !== "committed")
|
|
119
78
|
return null;
|
|
120
79
|
const action = committedAction(result.action);
|
|
@@ -129,12 +88,27 @@ function viewFromSnapshot(input) {
|
|
|
129
88
|
const beforeEvidence = snapshots(snapshot.beforeEvidence);
|
|
130
89
|
const afterEvidence = snapshots(snapshot.afterEvidence);
|
|
131
90
|
const observationDelta = deriveRoundObservationDelta(beforeEvidence, afterEvidence);
|
|
91
|
+
const contractProposal = evaluation?.round_contract
|
|
92
|
+
? parseRoundContract(evaluation.round_contract) ?? null
|
|
93
|
+
: null;
|
|
132
94
|
return {
|
|
133
95
|
source: input.source,
|
|
134
96
|
sourceEntry: input.sourceEntry,
|
|
135
97
|
loopId,
|
|
136
98
|
round,
|
|
137
99
|
roundId: snapshot.roundId,
|
|
100
|
+
transactionId: snapshot.transactionId,
|
|
101
|
+
branchId: snapshot.branchId,
|
|
102
|
+
kind: snapshot.kind,
|
|
103
|
+
origin: snapshot.kind === "rollback"
|
|
104
|
+
? (snapshot.recovery?.origin ?? "machine")
|
|
105
|
+
: "evaluation",
|
|
106
|
+
recovery: snapshot.recovery ?? null,
|
|
107
|
+
beforeCheckpoint: snapshot.beforeCheckpoint ?? null,
|
|
108
|
+
checkpoints: snapshot.checkpoints ?? [],
|
|
109
|
+
restore: snapshot.restore ?? null,
|
|
110
|
+
operatorAcceptance: snapshot.operatorAcceptance ?? null,
|
|
111
|
+
compiled: snapshot.compiled ?? null,
|
|
138
112
|
sequence: input.sequence,
|
|
139
113
|
attempt: snapshot.attempt,
|
|
140
114
|
promptArtifact: snapshot.promptArtifact ?? null,
|
|
@@ -142,12 +116,16 @@ function viewFromSnapshot(input) {
|
|
|
142
116
|
executionReport: evaluation?.execution_report ?? null,
|
|
143
117
|
verificationFlags: flags(result.verificationFlags),
|
|
144
118
|
result,
|
|
119
|
+
evidencePosture: result.evidencePosture ?? null,
|
|
145
120
|
action,
|
|
146
|
-
|
|
147
|
-
outcome,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
:
|
|
121
|
+
agentSuccessClaim: result.agentSuccessClaim === true,
|
|
122
|
+
agentOutcome: result.agentOutcome ?? outcome,
|
|
123
|
+
...(evaluation && typeof evaluation.objective_refinement === "string" &&
|
|
124
|
+
evaluation.objective_refinement.trim().length > 0
|
|
125
|
+
? { objectiveRefinement: evaluation.objective_refinement }
|
|
126
|
+
: {}),
|
|
127
|
+
contractProposal,
|
|
128
|
+
contractRedirect: contractProposal?.supersedes ?? null,
|
|
151
129
|
contractBinding: snapshot.contractBinding ?? null,
|
|
152
130
|
beforeEvidence,
|
|
153
131
|
afterEvidence,
|
|
@@ -158,13 +136,17 @@ function viewFromSnapshot(input) {
|
|
|
158
136
|
outputSummary: evaluation?.output_summary,
|
|
159
137
|
constraintViolations: strings(evaluation?.constraint_violations),
|
|
160
138
|
discoveredConstraints: strings(evaluation?.discovered_constraints),
|
|
161
|
-
activeConstraints:
|
|
139
|
+
activeConstraints: snapshot.compiled
|
|
140
|
+
? [...snapshot.compiled.constraintsActive]
|
|
141
|
+
: [],
|
|
142
|
+
compiledRetirements: snapshot.compiled
|
|
143
|
+
? [...snapshot.compiled.constraintsRetired]
|
|
144
|
+
: [],
|
|
162
145
|
retractedConstraints: strings(evaluation?.retracted_constraints),
|
|
146
|
+
openQuestions: strings(evaluation?.open_questions),
|
|
147
|
+
resolvedOpenQuestionIds: strings(evaluation?.resolved_open_question_ids),
|
|
163
148
|
emergedSubtasks: strings(evaluation?.emerged_subtasks),
|
|
164
149
|
subgoalUpdates: subGoalUpdates(evaluation?.subgoal_updates),
|
|
165
|
-
backtrack: action === "backtrack"
|
|
166
|
-
? decodeBacktrackRecord(result)
|
|
167
|
-
: null,
|
|
168
150
|
};
|
|
169
151
|
}
|
|
170
152
|
export function decodeCommittedRound(entry) {
|
|
@@ -181,66 +163,68 @@ export function decodeCommittedRound(entry) {
|
|
|
181
163
|
// legacyTransactionRounds() reports the loss explicitly; it is never
|
|
182
164
|
// silently decoded into a partial view.
|
|
183
165
|
const snapshot = parseRoundTransactionSnapshot(transaction.snapshot);
|
|
184
|
-
if (!snapshot)
|
|
166
|
+
if (!snapshot || snapshot.kind !== "evaluation")
|
|
167
|
+
return null;
|
|
168
|
+
const identityIssue = transactionIdentityIssue(entry);
|
|
169
|
+
if (identityIssue)
|
|
185
170
|
return null;
|
|
186
171
|
const resultValue = isRecord(transaction.result)
|
|
187
172
|
? transaction.result
|
|
188
173
|
: snapshot.result;
|
|
189
|
-
|
|
174
|
+
// v4.0.3/v4.0.6: a current-schema (9) committed result must carry a
|
|
175
|
+
// well-formed Evidence Posture and a well-formed Agent outcome. A missing or
|
|
176
|
+
// corrupt posture/result is a HARD break, never re-derived at read time.
|
|
177
|
+
if (!isRecord(resultValue) || !isProcessResult(resultValue))
|
|
190
178
|
return null;
|
|
191
179
|
return viewFromSnapshot({
|
|
192
180
|
source: "feedback",
|
|
193
181
|
sourceEntry: entry,
|
|
194
|
-
lineage,
|
|
195
182
|
snapshot,
|
|
196
183
|
result: resultValue,
|
|
197
184
|
sequence: typeof entry.sequence === "number" ? entry.sequence : undefined,
|
|
198
185
|
});
|
|
199
186
|
}
|
|
200
|
-
/**
|
|
201
|
-
*
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
export function decodeMergedRound(entry) {
|
|
205
|
-
if (!isRecord(entry))
|
|
206
|
-
return null;
|
|
207
|
-
const raw = entry;
|
|
208
|
-
const lineage = isRecord(raw.loop_lineage) ? raw.loop_lineage : {};
|
|
209
|
-
const action = committedAction(lineage.committed_action ?? raw.committed_action);
|
|
210
|
-
if (!action)
|
|
187
|
+
/** v3.9.6: decode a committed rollback directive event. It is a typed event
|
|
188
|
+
* in the round document, never a normal feedback round. */
|
|
189
|
+
export function decodeRollbackEvent(entry) {
|
|
190
|
+
if (entry.task_type !== "rollback_directive")
|
|
211
191
|
return null;
|
|
212
|
-
const
|
|
192
|
+
const lineage = isRecord(entry.loop_lineage) ? entry.loop_lineage : null;
|
|
193
|
+
const transaction = lineage && isRecord(lineage.round_transaction)
|
|
213
194
|
? lineage.round_transaction
|
|
214
|
-
:
|
|
215
|
-
? raw.round_transaction
|
|
216
|
-
: null;
|
|
195
|
+
: null;
|
|
217
196
|
if (!transaction)
|
|
218
197
|
return null;
|
|
219
198
|
const snapshot = parseRoundTransactionSnapshot(transaction.snapshot);
|
|
220
|
-
if (!snapshot)
|
|
199
|
+
if (!snapshot || snapshot.kind !== "rollback" || !snapshot.recovery)
|
|
200
|
+
return null;
|
|
201
|
+
const identityIssue = transactionIdentityIssue(entry);
|
|
202
|
+
if (identityIssue)
|
|
221
203
|
return null;
|
|
222
204
|
const resultValue = isRecord(transaction.result)
|
|
223
205
|
? transaction.result
|
|
224
206
|
: snapshot.result;
|
|
225
|
-
if (!isRecord(resultValue))
|
|
207
|
+
if (!isRecord(resultValue) || !isProcessResult(resultValue))
|
|
208
|
+
return null;
|
|
209
|
+
const result = resultValue;
|
|
210
|
+
if (result.action !== "recovery" && result.action !== "backtrack")
|
|
226
211
|
return null;
|
|
227
212
|
return viewFromSnapshot({
|
|
228
|
-
source: "
|
|
229
|
-
sourceEntry:
|
|
230
|
-
lineage,
|
|
213
|
+
source: "rollback",
|
|
214
|
+
sourceEntry: entry,
|
|
231
215
|
snapshot,
|
|
232
|
-
result
|
|
233
|
-
sequence: typeof
|
|
216
|
+
result,
|
|
217
|
+
sequence: typeof entry.sequence === "number" ? entry.sequence : undefined,
|
|
234
218
|
});
|
|
235
219
|
}
|
|
236
220
|
/** Normalize committed history: rollback directives disappear, later records
|
|
237
221
|
* replace earlier records for the same logical round, and output is ascending. */
|
|
238
|
-
/**
|
|
239
|
-
* merged
|
|
240
|
-
*
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
222
|
+
/** v4.0.1: decode either an ordinary committed round or a rollback event.
|
|
223
|
+
* There is no merged-lineage representation any more: a committed round is
|
|
224
|
+
* exactly its durable :feedback envelope, and a rollback is its event. */
|
|
225
|
+
export function decodeCommittedRecord(entry) {
|
|
226
|
+
return decodeCommittedRound(entry) ??
|
|
227
|
+
decodeRollbackEvent(entry);
|
|
244
228
|
}
|
|
245
229
|
/** THE committed-view → compiler-request projection.
|
|
246
230
|
*
|
|
@@ -248,56 +232,38 @@ export function decodeRound(entry) {
|
|
|
248
232
|
* `last_round_result`, which existed on the live path and was absent on
|
|
249
233
|
* resume/unpause/backtrack compiles. This projection lets one committed
|
|
250
234
|
* fact source feed every consumer. Optional declarations are read from the
|
|
251
|
-
* decoded view where it exposes them
|
|
252
|
-
* committed source and stays empty. */
|
|
235
|
+
* decoded view where it exposes them. */
|
|
253
236
|
export function roundResultFromCommittedRound(view) {
|
|
254
237
|
const evaluation = view.evaluation;
|
|
255
238
|
return makeLoopRoundResult({
|
|
256
239
|
round: view.round,
|
|
257
|
-
|
|
240
|
+
agentSuccessClaim: view.agentSuccessClaim,
|
|
258
241
|
output_summary: evaluation?.output_summary ?? "",
|
|
259
242
|
constraint_violations: [...(evaluation?.constraint_violations ?? [])],
|
|
260
|
-
manual_fixes_needed: "",
|
|
261
243
|
discovered_constraints: [...(view.discoveredConstraints ?? [])],
|
|
262
|
-
objective_refinement: evaluation?.objective_refinement ?? "",
|
|
263
244
|
emerged_subtasks: [...(view.emergedSubtasks ?? [])],
|
|
264
245
|
subgoal_updates: [...(view.subgoalUpdates ?? [])],
|
|
265
246
|
execution_report: view.executionReport ?? undefined,
|
|
266
247
|
retracted_constraints: [...(view.retractedConstraints ?? [])],
|
|
267
|
-
revised_success_criteria: [...(evaluation?.revised_success_criteria ?? [])],
|
|
268
248
|
wrong_assumptions: [...(evaluation?.wrong_assumptions ?? [])],
|
|
249
|
+
open_questions: [...(evaluation?.open_questions ?? [])],
|
|
250
|
+
resolved_open_question_ids: [...(evaluation?.resolved_open_question_ids ?? [])],
|
|
269
251
|
worker_results: [...(evaluation?.worker_results ?? [])],
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
252
|
+
subtask_boundary: evaluation?.subtask_boundary === true,
|
|
253
|
+
subtask_boundary_label: evaluation?.subtask_boundary_label ?? "",
|
|
254
|
+
agentOutcome: evaluation
|
|
255
|
+
? effectiveOutcome(evaluation)
|
|
256
|
+
: view.agentOutcome ?? undefined,
|
|
274
257
|
blocker: evaluation?.blocker,
|
|
275
258
|
retroactiveClaims: [...(evaluation?.retroactiveClaims ?? [])],
|
|
276
259
|
no_change_reason: evaluation?.no_change_reason,
|
|
277
260
|
});
|
|
278
261
|
}
|
|
279
|
-
/** The ONE pending-
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* `backtrackTarget + 1`, which is lower than R for a deep restore. Matching
|
|
283
|
-
* the decoded target (with the legacy exact-round fallback when the record
|
|
284
|
-
* carries no target) keeps the recovery boundary AND the Recovery Brief
|
|
285
|
-
* correct for every backtrack depth. */
|
|
262
|
+
/** The ONE pending-directive lookup for a redo round. The directive's
|
|
263
|
+
* `target.redo_round` is the branch fence, so a deep restore and a
|
|
264
|
+
* same-round redo match the same way. */
|
|
286
265
|
export function pendingBacktrackRecord(entries, redoRound) {
|
|
287
|
-
|
|
288
|
-
const view = decodeRound(entries[index]);
|
|
289
|
-
if (!view || view.action !== "backtrack")
|
|
290
|
-
continue;
|
|
291
|
-
const target = view.backtrack?.target;
|
|
292
|
-
if (typeof target === "number") {
|
|
293
|
-
if (target + 1 === redoRound)
|
|
294
|
-
return view;
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
if (view.round === redoRound)
|
|
298
|
-
return view;
|
|
299
|
-
}
|
|
300
|
-
return null;
|
|
266
|
+
return pendingRollbackDirective(entries, redoRound);
|
|
301
267
|
}
|
|
302
268
|
/** Lineage sub-object of an entry (`loop_lineage`; v3.7: the legacy
|
|
303
269
|
* top-level `lineage` alias was removed), or {} when absent. */
|
|
@@ -307,188 +273,114 @@ export function entryLineage(entry) {
|
|
|
307
273
|
const value = entry.loop_lineage;
|
|
308
274
|
return isRecord(value) ? value : {};
|
|
309
275
|
}
|
|
310
|
-
/**
|
|
311
|
-
*
|
|
312
|
-
* a
|
|
313
|
-
*
|
|
314
|
-
export function
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
.
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
/** Read constraints_active from an entry's lineage. */
|
|
357
|
-
export function entryActiveConstraints(entry) {
|
|
358
|
-
const view = decodeRound(entry);
|
|
359
|
-
if (view)
|
|
360
|
-
return view.activeConstraints ?? [];
|
|
361
|
-
const arr = entryLineage(entry).constraints_active;
|
|
362
|
-
return Array.isArray(arr) ? arr.filter((v) => typeof v === "string") : [];
|
|
363
|
-
}
|
|
364
|
-
/** Read retracted_constraints from an entry (direct field, else nested in
|
|
365
|
-
* the lineage). */
|
|
366
|
-
export function entryRetractedConstraints(entry) {
|
|
367
|
-
const view = decodeRound(entry);
|
|
368
|
-
if (view)
|
|
369
|
-
return view.retractedConstraints ?? [];
|
|
370
|
-
const record = isRecord(entry) ? entry : {};
|
|
371
|
-
const direct = record.retracted_constraints;
|
|
372
|
-
if (Array.isArray(direct))
|
|
373
|
-
return direct.filter((v) => typeof v === "string");
|
|
374
|
-
const nested = entryLineage(entry).retracted_constraints;
|
|
375
|
-
if (Array.isArray(nested))
|
|
376
|
-
return nested.filter((v) => typeof v === "string");
|
|
377
|
-
return [];
|
|
378
|
-
}
|
|
379
|
-
/** Read emerged_subtasks from an entry (handles direct + lineage nesting). */
|
|
380
|
-
export function entryEmergedSubtasks(entry) {
|
|
381
|
-
const view = decodeRound(entry);
|
|
382
|
-
if (view)
|
|
383
|
-
return view.emergedSubtasks ?? [];
|
|
384
|
-
const record = isRecord(entry) ? entry : {};
|
|
385
|
-
const direct = record.emerged_subtasks;
|
|
386
|
-
if (Array.isArray(direct))
|
|
387
|
-
return direct.filter((v) => typeof v === "string");
|
|
388
|
-
const nested = entryLineage(entry).emerged_subtasks;
|
|
389
|
-
if (Array.isArray(nested))
|
|
390
|
-
return nested.filter((v) => typeof v === "string");
|
|
391
|
-
return [];
|
|
392
|
-
}
|
|
393
|
-
/** Read subgoal_updates from an entry (handles direct + lineage nesting).
|
|
394
|
-
* v3.7.1: committed status transitions are replayed in round order by the
|
|
395
|
-
* compiler — every derivation replays ALL committed rounds, not just the
|
|
396
|
-
* last one. */
|
|
397
|
-
export function entrySubGoalUpdates(entry) {
|
|
398
|
-
const view = decodeRound(entry);
|
|
399
|
-
if (view)
|
|
400
|
-
return view.subgoalUpdates ?? [];
|
|
401
|
-
const record = isRecord(entry) ? entry : {};
|
|
402
|
-
const direct = record.subgoal_updates;
|
|
403
|
-
if (Array.isArray(direct))
|
|
404
|
-
return subGoalUpdates(direct);
|
|
405
|
-
const nested = entryLineage(entry).subgoal_updates;
|
|
406
|
-
if (Array.isArray(nested))
|
|
407
|
-
return subGoalUpdates(nested);
|
|
408
|
-
return [];
|
|
409
|
-
}
|
|
410
|
-
/** Read constraint_violations from an entry (entry-level, stored from the
|
|
411
|
-
* previous round's last_round_result at persist time — distinct from the
|
|
412
|
-
* evaluation-level violations on the committed view). Moved from
|
|
413
|
-
* verification-gate so lineage-shape reads live in this module only. */
|
|
414
|
-
export function entryViolations(entry) {
|
|
415
|
-
const record = isRecord(entry) ? entry : {};
|
|
416
|
-
const viols = record.constraint_violations;
|
|
417
|
-
if (Array.isArray(viols))
|
|
418
|
-
return viols.filter((v) => typeof v === "string");
|
|
419
|
-
return [];
|
|
420
|
-
}
|
|
421
|
-
export function historyRounds(views, beforeRound = Number.POSITIVE_INFINITY) {
|
|
276
|
+
/** Resolve the current branch from already-decoded committed views. This is
|
|
277
|
+
* the branch walker itself; `resolveBranch` is only the entry-decoding
|
|
278
|
+
* wrapper, so a caller that already holds the views (the history loader)
|
|
279
|
+
* never decodes the same entries twice. */
|
|
280
|
+
export function resolveBranchViews(records) {
|
|
281
|
+
const kindOf = (view) => view.kind ?? "evaluation";
|
|
282
|
+
const first = records.find((view) => kindOf(view) === "evaluation") ?? records[0];
|
|
283
|
+
if (!first) {
|
|
284
|
+
return { loopId: "", branchId: "", rounds: [], directives: [], pendingDirective: null };
|
|
285
|
+
}
|
|
286
|
+
const loopId = first.loopId;
|
|
287
|
+
const branchOf = (view) => view.branchId ?? rootBranchId(loopId);
|
|
288
|
+
let branchId = rootBranchId(loopId);
|
|
289
|
+
let rounds = records
|
|
290
|
+
.filter((view) => kindOf(view) === "evaluation" && branchOf(view) === branchId)
|
|
291
|
+
.sort((a, b) => a.round - b.round);
|
|
292
|
+
const directives = [];
|
|
293
|
+
const seen = new Set();
|
|
294
|
+
for (;;) {
|
|
295
|
+
const candidates = records
|
|
296
|
+
.filter((view) => kindOf(view) === "rollback" &&
|
|
297
|
+
branchOf(view) === branchId &&
|
|
298
|
+
view.recovery !== null && view.recovery !== undefined)
|
|
299
|
+
.sort((a, b) => a.round - b.round ||
|
|
300
|
+
(a.transactionId ?? "").localeCompare(b.transactionId ?? ""));
|
|
301
|
+
if (candidates.length === 0)
|
|
302
|
+
break;
|
|
303
|
+
// A branch closes exactly once. If corrupt data has more, the latest one
|
|
304
|
+
// is the effective directive; the others remain visible in the raw log.
|
|
305
|
+
const directive = candidates[candidates.length - 1];
|
|
306
|
+
const directiveId = directive.transactionId ?? directive.roundId;
|
|
307
|
+
if (seen.has(directiveId))
|
|
308
|
+
break;
|
|
309
|
+
seen.add(directiveId);
|
|
310
|
+
const recovery = directive.recovery;
|
|
311
|
+
// v3.9.7: the fence is the explicit redo round, not a nominal target
|
|
312
|
+
// round. A before/mid checkpoint's redo round is its owning round.
|
|
313
|
+
rounds = rounds.filter((view) => view.round < recovery.target.redo_round);
|
|
314
|
+
const child = recovery.transaction_id;
|
|
315
|
+
const childRounds = records
|
|
316
|
+
.filter((view) => kindOf(view) === "evaluation" && branchOf(view) === child)
|
|
317
|
+
.sort((a, b) => a.round - b.round);
|
|
318
|
+
rounds = [...rounds, ...childRounds];
|
|
319
|
+
directives.push(directive);
|
|
320
|
+
branchId = child;
|
|
321
|
+
}
|
|
422
322
|
const byRound = new Map();
|
|
423
|
-
for (const view of
|
|
424
|
-
if (!view || view.action === "backtrack" || view.round >= beforeRound)
|
|
425
|
-
continue;
|
|
323
|
+
for (const view of rounds)
|
|
426
324
|
byRound.set(view.round, view);
|
|
325
|
+
rounds = [...byRound.values()].sort((a, b) => a.round - b.round);
|
|
326
|
+
let pendingDirective = null;
|
|
327
|
+
const latest = directives[directives.length - 1];
|
|
328
|
+
if (latest && latest.recovery) {
|
|
329
|
+
const child = latest.recovery.transaction_id;
|
|
330
|
+
const hasChildRound = records.some((view) => kindOf(view) === "evaluation" && branchOf(view) === child);
|
|
331
|
+
if (!hasChildRound)
|
|
332
|
+
pendingDirective = latest;
|
|
333
|
+
}
|
|
334
|
+
return { loopId, branchId, rounds, directives, pendingDirective };
|
|
335
|
+
}
|
|
336
|
+
/** Resolve the current branch from committed records in the raw entry view.
|
|
337
|
+
* Decoding happens once here; the walker itself is `resolveBranchViews`. */
|
|
338
|
+
export function resolveBranch(entries) {
|
|
339
|
+
return resolveBranchViews(entries
|
|
340
|
+
.map(decodeCommittedRecord)
|
|
341
|
+
.filter((view) => view !== null));
|
|
342
|
+
}
|
|
343
|
+
/** v3.9.6: current-branch evaluation rounds. */
|
|
344
|
+
export function branchRounds(entries) {
|
|
345
|
+
return resolveBranch(entries).rounds;
|
|
346
|
+
}
|
|
347
|
+
/** v3.9.6: rollback directive chain. */
|
|
348
|
+
export function branchDirectives(entries) {
|
|
349
|
+
return resolveBranch(entries).directives;
|
|
350
|
+
}
|
|
351
|
+
/** v3.9.6: the pending directive controlling a redo round. */
|
|
352
|
+
export function pendingRollbackDirective(entries, redoRound) {
|
|
353
|
+
const pending = resolveBranch(entries).pendingDirective;
|
|
354
|
+
if (!pending?.recovery)
|
|
355
|
+
return null;
|
|
356
|
+
if (redoRound !== undefined && pending.recovery.target.redo_round !== redoRound) {
|
|
357
|
+
return null;
|
|
427
358
|
}
|
|
428
|
-
return
|
|
359
|
+
return pending;
|
|
360
|
+
}
|
|
361
|
+
/** v3.9.6: machine deadlock guard. Agent-origin directives never consume it. */
|
|
362
|
+
export function hasCommittedMachineBacktrack(entries, currentRound) {
|
|
363
|
+
return branchDirectives(entries).some((view) => {
|
|
364
|
+
const recovery = view.recovery;
|
|
365
|
+
return view.origin === "machine" &&
|
|
366
|
+
recovery !== null && recovery !== undefined &&
|
|
367
|
+
recovery.target.redo_round <= currentRound &&
|
|
368
|
+
view.round >= currentRound;
|
|
369
|
+
});
|
|
429
370
|
}
|
|
430
|
-
/** v3.
|
|
431
|
-
*
|
|
432
|
-
* `decodeRound` (hydrated merged lineage first, then the durable feedback
|
|
433
|
-
* entry) plus the rollback handling in `historyRounds` — so the compile path,
|
|
434
|
-
* the projection, the live coordinator, audit and explain can no longer
|
|
435
|
-
* disagree about which rounds are this branch's history, nor about how their
|
|
436
|
-
* envelope was interpreted.
|
|
437
|
-
*
|
|
438
|
-
* `currentRound` bounds the window exactly as before (default: unbounded). It
|
|
439
|
-
* is NOT a rollback fence: the redo re-commits the round numbers the abandoned
|
|
440
|
-
* rounds occupied, so abandonment is temporal, not numeric, and cannot be
|
|
441
|
-
* expressed as a bound here. */
|
|
371
|
+
/** v3.9.6: live history window = current branch below the round being
|
|
372
|
+
* compiled. `currentRound` is the round being prepared/submitted. */
|
|
442
373
|
export function derivationRounds(entries, currentRound = Number.POSITIVE_INFINITY) {
|
|
443
|
-
return
|
|
374
|
+
return branchRounds(entries).filter((view) => view.round < currentRound);
|
|
444
375
|
}
|
|
445
|
-
/** v3.
|
|
446
|
-
*
|
|
447
|
-
* callers fail OPEN rather than inventing a bound. */
|
|
448
|
-
export function persistedFrontier(entries) {
|
|
449
|
-
for (const entry of entries) {
|
|
450
|
-
if (!isRecord(entry) || entry.task_type !== "session_state")
|
|
451
|
-
continue;
|
|
452
|
-
const round = entryLineage(entry).current_round;
|
|
453
|
-
return typeof round === "number" && Number.isFinite(round) && round >= 1
|
|
454
|
-
? round
|
|
455
|
-
: null;
|
|
456
|
-
}
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
/** v3.8.1: THE window for a READ-ONLY view of a loop — audit, explain, replay,
|
|
460
|
-
* the projection, the metrics replay. Same decoder and same rollback handling
|
|
461
|
-
* as `derivationRounds`, plus the bound those callers cannot compute
|
|
462
|
-
* themselves.
|
|
463
|
-
*
|
|
464
|
-
* The live paths bound their window at the round being compiled, and that
|
|
465
|
-
* bound is what keeps a backtracked branch out of this branch's history. A
|
|
466
|
-
* read-only view has no compile round, so it reads the SAME frontier from the
|
|
467
|
-
* durable session document and applies it only while a committed rollback
|
|
468
|
-
* directive is still in effect — a directive stops being history the moment
|
|
469
|
-
* the redo commit replaces its round's record, which is exactly when the
|
|
470
|
-
* abandoned rounds have been re-committed and must count again.
|
|
471
|
-
*
|
|
472
|
-
* With no directive in effect the window stays UNBOUNDED on purpose: after a
|
|
473
|
-
* stop, terminate or max_rounds the session's `current_round` sits ON the
|
|
474
|
-
* round it just committed, so bounding there would silently drop the loop's
|
|
475
|
-
* final round. An unobservable frontier fails open the same way — a read-only
|
|
476
|
-
* view never invents a fence, it only applies the one the runtime recorded. */
|
|
376
|
+
/** v3.9.6: read-only history = current branch rounds. No session frontier,
|
|
377
|
+
* no numeric fence, no second cursor. */
|
|
477
378
|
export function readOnlyRounds(entries) {
|
|
478
|
-
|
|
479
|
-
const rollbackPending = views.some((view) => view?.action === "backtrack");
|
|
480
|
-
if (!rollbackPending)
|
|
481
|
-
return historyRounds(views);
|
|
482
|
-
return historyRounds(views, persistedFrontier(entries) ?? Number.POSITIVE_INFINITY);
|
|
379
|
+
return branchRounds(entries);
|
|
483
380
|
}
|
|
484
|
-
/**
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
* over these facts, not a second observation collection. */
|
|
488
|
-
export function machineEvidenceForRound(view) {
|
|
489
|
-
if (view.afterEvidence.length > 0)
|
|
490
|
-
return view.afterEvidence;
|
|
491
|
-
return view.beforeEvidence;
|
|
381
|
+
/** v3.9.6: read-only rollback events = committed directive chain. */
|
|
382
|
+
export function readOnlyRollbacks(entries) {
|
|
383
|
+
return branchDirectives(entries);
|
|
492
384
|
}
|
|
493
385
|
export function legacyTransactionRounds(entries) {
|
|
494
386
|
const out = [];
|
|
@@ -516,12 +408,42 @@ export function legacyTransactionRounds(entries) {
|
|
|
516
408
|
// silently, which is the one thing this function exists to prevent.
|
|
517
409
|
const snapshot = envelope.snapshot;
|
|
518
410
|
if (isRecord(snapshot.promptArtifact)) {
|
|
519
|
-
const
|
|
411
|
+
const artifact = snapshot.promptArtifact;
|
|
412
|
+
const artifactVersion = artifact.schemaVersion;
|
|
520
413
|
if (typeof artifactVersion === "number" &&
|
|
521
414
|
artifactVersion !== PROMPT_ARTIFACT_SCHEMA_VERSION) {
|
|
522
415
|
out.push({ round, schemaVersion: artifactVersion, envelope: "prompt_artifact" });
|
|
523
416
|
continue;
|
|
524
417
|
}
|
|
418
|
+
// v4.0.5: the same runtime whitelists the decoder uses. A malformed
|
|
419
|
+
// optional invocation instruction is a legacy artifact, not accepted
|
|
420
|
+
// silently.
|
|
421
|
+
const validInstruction = artifact.currentUserInstruction === undefined ||
|
|
422
|
+
(typeof artifact.currentUserInstruction === "string" &&
|
|
423
|
+
artifact.currentUserInstruction.trim().length > 0);
|
|
424
|
+
const validContextFields = (artifact.level === "l0" || artifact.level === "l1" || artifact.level === "l2") &&
|
|
425
|
+
isPromptContextMode(artifact.contextMode) &&
|
|
426
|
+
isPromptContextReason(artifact.contextReason) &&
|
|
427
|
+
validInstruction &&
|
|
428
|
+
typeof artifact.attempt === "number" &&
|
|
429
|
+
typeof artifact.round === "number";
|
|
430
|
+
const contextIssue = validContextFields
|
|
431
|
+
? promptContextPairIssue({
|
|
432
|
+
level: artifact.level,
|
|
433
|
+
mode: artifact.contextMode,
|
|
434
|
+
reason: artifact.contextReason,
|
|
435
|
+
attempt: artifact.attempt,
|
|
436
|
+
round: artifact.round,
|
|
437
|
+
})
|
|
438
|
+
: "missing or malformed context boundary fields";
|
|
439
|
+
if (contextIssue !== null) {
|
|
440
|
+
out.push({
|
|
441
|
+
round,
|
|
442
|
+
schemaVersion: typeof artifactVersion === "number" ? artifactVersion : -1,
|
|
443
|
+
envelope: "prompt_context",
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
525
447
|
}
|
|
526
448
|
// v3.9.1: an observation schema mismatch is also a hard break. The
|
|
527
449
|
// transaction parser drops the whole round on it; report the loss here
|
|
@@ -531,16 +453,16 @@ export function legacyTransactionRounds(entries) {
|
|
|
531
453
|
...(Array.isArray(snapshot.afterEvidence) ? snapshot.afterEvidence : []),
|
|
532
454
|
];
|
|
533
455
|
for (const item of observations) {
|
|
534
|
-
if (!isRecord(item)
|
|
456
|
+
if (!isRecord(item)) {
|
|
535
457
|
out.push({ round, schemaVersion: -1, envelope: "observation" });
|
|
536
458
|
break;
|
|
537
459
|
}
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
if (
|
|
460
|
+
// v3.9.9: the SAME version judgment the transaction parser uses.
|
|
461
|
+
const issue = observationVersionIssue(item.kind, item.schemaVersion, item.data, item.status);
|
|
462
|
+
if (issue) {
|
|
541
463
|
out.push({
|
|
542
464
|
round,
|
|
543
|
-
schemaVersion:
|
|
465
|
+
schemaVersion: issue.schemaVersion,
|
|
544
466
|
envelope: "observation",
|
|
545
467
|
});
|
|
546
468
|
break;
|
|
@@ -549,122 +471,4 @@ export function legacyTransactionRounds(entries) {
|
|
|
549
471
|
}
|
|
550
472
|
return out.sort((a, b) => a.round - b.round);
|
|
551
473
|
}
|
|
552
|
-
/** The ONE definition of machine Git motion for a single round.
|
|
553
|
-
*
|
|
554
|
-
* Motion is a non-empty file list, a HEAD that moved across the round, or a
|
|
555
|
-
* HEAD that landed on the rollback's restore point — restoring the workspace
|
|
556
|
-
* IS that round's work, and would otherwise read as a round that did
|
|
557
|
-
* nothing. A clean worktree is not the same thing as no progress:
|
|
558
|
-
* commit-per-slice workflows leave no file delta even though HEAD advanced.
|
|
559
|
-
*
|
|
560
|
-
* Observability stays the caller's concern: this answers "did this round
|
|
561
|
-
* move", never "could the round be observed". The restore-point arm compares
|
|
562
|
-
* short hashes, the rule `checkBacktrackGitHeadRestore` uses; the
|
|
563
|
-
* before/after arm compares the observations themselves. */
|
|
564
|
-
export function gitMotionSignal(input) {
|
|
565
|
-
if (input.changedFiles !== null && input.changedFiles.length > 0)
|
|
566
|
-
return true;
|
|
567
|
-
const { headBefore, headAfter, restoreTargetHead } = input;
|
|
568
|
-
if (headAfter && headBefore && headAfter !== headBefore)
|
|
569
|
-
return true;
|
|
570
|
-
if (headAfter && restoreTargetHead &&
|
|
571
|
-
headAfter.slice(0, 12) === restoreTargetHead.slice(0, 12))
|
|
572
|
-
return true;
|
|
573
|
-
if (input.changedFiles === null)
|
|
574
|
-
return null;
|
|
575
|
-
// An observed empty change set is no motion only when both HEADs are known;
|
|
576
|
-
// otherwise a commit-only slice could still have moved HEAD.
|
|
577
|
-
if (!headBefore || !headAfter)
|
|
578
|
-
return null;
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
581
|
-
/** Git HEAD as a machine observation exposes it, or null when it is silent. */
|
|
582
|
-
export function observedGitHead(observation) {
|
|
583
|
-
return observation && isRecord(observation.data) &&
|
|
584
|
-
typeof observation.data.head === "string" && observation.data.head
|
|
585
|
-
? observation.data.head
|
|
586
|
-
: null;
|
|
587
|
-
}
|
|
588
|
-
function observedGitObservation(observation) {
|
|
589
|
-
return observation && observation.kind === "git" && observation.status === "observed"
|
|
590
|
-
? observation
|
|
591
|
-
: null;
|
|
592
|
-
}
|
|
593
|
-
function observedGitIn(observations) {
|
|
594
|
-
return observations.find((observation) => observation.kind === "git" && observation.status === "observed") ?? null;
|
|
595
|
-
}
|
|
596
|
-
/** A round's own motion, from its derived Git deltas. A Git boundary that
|
|
597
|
-
* cannot be compared yields `null` unless HEAD movement or the restore point
|
|
598
|
-
* independently proves motion. Every Git delta participates: one moving
|
|
599
|
-
* provider is motion, one undecidable provider makes the aggregate unknown,
|
|
600
|
-
* and only an all-provider observed no-change is `false`. */
|
|
601
|
-
export function inFlightGitMotion(deltas, restoreTargetHead) {
|
|
602
|
-
const gitDeltas = deltas.filter((delta) => delta.kind === "git");
|
|
603
|
-
if (gitDeltas.length === 0)
|
|
604
|
-
return null;
|
|
605
|
-
let sawUnknown = false;
|
|
606
|
-
for (const git of gitDeltas) {
|
|
607
|
-
const before = observedGitObservation(git.before);
|
|
608
|
-
const after = observedGitObservation(git.after);
|
|
609
|
-
const signal = gitMotionSignal({
|
|
610
|
-
changedFiles: git.changedFiles,
|
|
611
|
-
headBefore: observedGitHead(before ?? undefined),
|
|
612
|
-
headAfter: observedGitHead(after ?? undefined),
|
|
613
|
-
restoreTargetHead: restoreTargetHead ?? null,
|
|
614
|
-
});
|
|
615
|
-
if (signal === true)
|
|
616
|
-
return true;
|
|
617
|
-
if (signal === null)
|
|
618
|
-
sawUnknown = true;
|
|
619
|
-
}
|
|
620
|
-
return sawUnknown ? null : false;
|
|
621
|
-
}
|
|
622
|
-
/** The committed-round form of the same motion rule. Named separately so a
|
|
623
|
-
* reader can see that prompt freshness never passes an in-flight restore
|
|
624
|
-
* head (a committed round has no pending backtrack of its own). */
|
|
625
|
-
export function committedRoundGitMotion(view) {
|
|
626
|
-
return inFlightGitMotion(view.observationDelta);
|
|
627
|
-
}
|
|
628
|
-
/** Machine-observed git motion for the most recent contiguous window.
|
|
629
|
-
* A clean worktree is not the same thing as no progress: commit-per-slice
|
|
630
|
-
* workflows leave no file delta even though HEAD advanced. Unknown motion in
|
|
631
|
-
* any round makes the window undecidable rather than falsely empty. */
|
|
632
|
-
export function machineGitMotionSeries(rounds, currentRound, lookback) {
|
|
633
|
-
if (!Number.isInteger(lookback) || lookback < 1 || currentRound <= lookback)
|
|
634
|
-
return null;
|
|
635
|
-
const byRound = new Map();
|
|
636
|
-
for (const round of rounds) {
|
|
637
|
-
if (round.round < 1 || round.round >= currentRound)
|
|
638
|
-
continue;
|
|
639
|
-
byRound.set(round.round, round);
|
|
640
|
-
}
|
|
641
|
-
const recent = [];
|
|
642
|
-
for (let number = currentRound - lookback; number < currentRound; number++) {
|
|
643
|
-
const round = byRound.get(number);
|
|
644
|
-
if (!round)
|
|
645
|
-
return null;
|
|
646
|
-
if (!round.observationDelta.some((delta) => delta.kind === "git"))
|
|
647
|
-
return null;
|
|
648
|
-
recent.push(round);
|
|
649
|
-
}
|
|
650
|
-
const motion = [];
|
|
651
|
-
for (const round of recent) {
|
|
652
|
-
const git = round.observationDelta.find((delta) => delta.kind === "git");
|
|
653
|
-
// The round's baseline: its own before observation, else the previous
|
|
654
|
-
// round's after observation. Older Git observations do not carry a HEAD
|
|
655
|
-
// field; their change set is still a valid motion signal.
|
|
656
|
-
const baseline = observedGitObservation(git.before)
|
|
657
|
-
?? observedGitIn(byRound.get(round.round - 1)?.afterEvidence ?? []);
|
|
658
|
-
const after = observedGitObservation(git.after);
|
|
659
|
-
const signal = gitMotionSignal({
|
|
660
|
-
changedFiles: git.changedFiles,
|
|
661
|
-
headBefore: observedGitHead(baseline ?? undefined),
|
|
662
|
-
headAfter: observedGitHead(after ?? undefined),
|
|
663
|
-
});
|
|
664
|
-
if (signal === null)
|
|
665
|
-
return null;
|
|
666
|
-
motion.push(signal);
|
|
667
|
-
}
|
|
668
|
-
return motion;
|
|
669
|
-
}
|
|
670
474
|
//# sourceMappingURL=committed-round.js.map
|