loopforge 3.9.2 → 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 +152 -40
- package/dist/canonical-state.d.ts.map +1 -1
- package/dist/canonical-state.js +382 -206
- 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 +105 -141
- package/dist/committed-round.d.ts.map +1 -1
- package/dist/committed-round.js +249 -372
- 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 +32 -67
- package/dist/enforcement-gate.d.ts.map +1 -1
- package/dist/enforcement-gate.js +203 -439
- 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 +28 -887
- 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 +167 -27
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +373 -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 +51 -19
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +423 -507
- 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 +285 -81
- package/dist/mcp/round-lifecycle.d.ts.map +1 -1
- package/dist/mcp/round-lifecycle.js +1629 -574
- package/dist/mcp/round-lifecycle.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +61 -19
- 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 +963 -271
- 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 -82
- 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 +763 -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 +15 -40
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +13 -13
- 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 +28 -9
- package/dist/prompt-assembler.d.ts.map +1 -1
- package/dist/prompt-assembler.js +592 -603
- package/dist/prompt-assembler.js.map +1 -1
- package/dist/prompt-coverage.d.ts +39 -0
- package/dist/prompt-coverage.d.ts.map +1 -0
- package/dist/prompt-coverage.js +80 -0
- package/dist/prompt-coverage.js.map +1 -0
- package/dist/prompt-freshness.d.ts +25 -0
- package/dist/prompt-freshness.d.ts.map +1 -0
- package/dist/prompt-freshness.js +88 -0
- package/dist/prompt-freshness.js.map +1 -0
- package/dist/prompt-policy.d.ts +14 -4
- package/dist/prompt-policy.d.ts.map +1 -1
- package/dist/prompt-policy.js +29 -10
- package/dist/prompt-policy.js.map +1 -1
- package/dist/protocol.d.ts +875 -419
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +73 -99
- 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 +45 -91
- package/dist/round-coordinator.d.ts.map +1 -1
- package/dist/round-coordinator.js +254 -271
- 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 +54 -157
- 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 +856 -39
- package/dist/round-transaction.js.map +1 -1
- package/dist/self-eval.d.ts +40 -60
- package/dist/self-eval.d.ts.map +1 -1
- package/dist/self-eval.js +455 -384
- 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 +43 -21
- package/dist/submission-guidance.d.ts.map +1 -1
- package/dist/submission-guidance.js +368 -262
- 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 +146 -51
- package/dist/submission-receipt.js.map +1 -1
- package/dist/token-utils.d.ts +47 -7
- package/dist/token-utils.d.ts.map +1 -1
- package/dist/token-utils.js +66 -15
- 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 +13 -0
- package/dist/verification-actions.d.ts.map +1 -0
- package/dist/verification-actions.js +44 -0
- package/dist/verification-actions.js.map +1 -0
- package/dist/verification-gate.d.ts +26 -46
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +218 -427
- 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 +5 -19
- package/package.json +2 -2
- 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 -370
|
@@ -21,14 +21,16 @@
|
|
|
21
21
|
import { afterObservations, gitDeltaState } from "./evidence-provider.js";
|
|
22
22
|
import { deriveFilesReconciliation } from "./files-reconciliation.js";
|
|
23
23
|
import { isPassedAfterObservation } from "./evidence-provider.js";
|
|
24
|
-
import { getPolicy } from "./policy.js";
|
|
24
|
+
import { deriveConfiguredCapability, getPolicy } from "./policy.js";
|
|
25
25
|
import { makeVerificationFlag, makeVerificationResult } from "./protocol.js";
|
|
26
|
-
import { isRecord,
|
|
27
|
-
import { derivationRounds,
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
26
|
+
import { isRecord, extractFilePathTokens, deriveConstraintId, deriveCriterionId, isCriterionId, normalizeText, } from "./token-utils.js";
|
|
27
|
+
import { derivationRounds, } from "./committed-round.js";
|
|
28
|
+
import { acceptanceItemsDiffer, sameContract, } from "./round-contract.js";
|
|
29
|
+
import { deriveContractRequirement } from "./contract-requirement.js";
|
|
30
|
+
import { deriveRoundFacts } from "./round-facts.js";
|
|
31
|
+
import { machineVerifiedCriterionIds, resolveRoundFiles, } from "./evidence-claims.js";
|
|
31
32
|
import { claimedMetCriteria, claimedRemainingCriteria, effectiveSuccess } from "./self-eval.js";
|
|
33
|
+
import { declaresBlocked } from "./round-outcome.js";
|
|
32
34
|
import { deriveGate, preflightStructuredGate } from "./cognitive-governance.js";
|
|
33
35
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
34
36
|
// v2.12: Check-name constants — single source of truth so the enforcement
|
|
@@ -44,11 +46,6 @@ import { deriveGate, preflightStructuredGate } from "./cognitive-governance.js";
|
|
|
44
46
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
47
|
export const CHECK_SUCCESS_WITH_REMAINING_CRITERIA = "success_with_remaining_criteria";
|
|
46
48
|
export const CHECK_SUCCESS_WITHOUT_VERIFIED_EVIDENCE = "success_without_verified_evidence";
|
|
47
|
-
// v3.7: single id for the outcome-vs-success axis — error when a declared
|
|
48
|
-
// success contradicts success=false, warn when success=true but the outcome
|
|
49
|
-
// declares a non-success (the former success_claim_conflict direction).
|
|
50
|
-
export const CHECK_OUTCOME_SUCCESS_CONTRADICTION = "outcome_success_contradiction";
|
|
51
|
-
export const CHECK_BLOCKED_WITHOUT_BLOCKER = "blocked_without_blocker";
|
|
52
49
|
export const CHECK_RETROACTIVE_CLAIM_BAD_ROUND = "retroactive_claim_bad_round";
|
|
53
50
|
export const CHECK_RETROACTIVE_CLAIM_UNVERIFIED = "retroactive_claim_unverified";
|
|
54
51
|
export const CHECK_DUPLICATE_CONSTRAINT_DISCOVERY = "duplicate_constraint_discovery";
|
|
@@ -56,7 +53,6 @@ export const CHECK_RECURRING_VIOLATION = "recurring_violation";
|
|
|
56
53
|
export const CHECK_RETRACT_FRESH_CONSTRAINT = "retract_fresh_constraint";
|
|
57
54
|
export const CHECK_EVIDENCE_INTEGRITY = "evidence_integrity";
|
|
58
55
|
export const CHECK_REQUIRED_COMMAND_FAILED = "required_command_failed";
|
|
59
|
-
export const CHECK_COMMAND_EVIDENCE_MISMATCH = "command_evidence_mismatch";
|
|
60
56
|
/** v3.3: Verification domain integrity — a command entrypoint (run-tests.sh,
|
|
61
57
|
* package.json, …) changed in the same round the command ran. The runtime
|
|
62
58
|
* executed a script the agent just rewrote, so the observation has no
|
|
@@ -66,7 +62,6 @@ export const CHECK_VERIFICATION_ENTRYPOINT_MODIFIED = "verification_entrypoint_m
|
|
|
66
62
|
/** v3.3: Test files changed in the same round a verification command passed.
|
|
67
63
|
* Warn-only — the command result stays usable. */
|
|
68
64
|
export const CHECK_TEST_FILES_MODIFIED = "test_files_modified";
|
|
69
|
-
export const CHECK_BACKTRACK_WORKSPACE_NOT_RESTORED = "backtrack_workspace_not_restored";
|
|
70
65
|
export const CHECK_CRITERIA_CLAIMS_UNVERIFIED = "criteria_claims_unverified";
|
|
71
66
|
// v3.3 — Round Contract checks. v3.4: split by target. The submission's own
|
|
72
67
|
// round_contract is a PROPOSAL for the NEXT round and is checked only
|
|
@@ -92,11 +87,19 @@ export const CHECK_CONTRACT_ITEMS_UNVERIFIED = "contract_items_unverified";
|
|
|
92
87
|
* contract's declared scope. Warn; v3.8: not waivable by explanation — the
|
|
93
88
|
* enforcement gate rejects, and repeated drift terminates. */
|
|
94
89
|
export const CHECK_ROUND_SCOPE_DRIFT = "round_scope_drift";
|
|
95
|
-
/** v3.5: The ACTIVE contract is still open (not completed, not
|
|
96
|
-
* while a different contract was proposed
|
|
97
|
-
*
|
|
98
|
-
*
|
|
90
|
+
/** v3.5/v4.0.6: The ACTIVE contract is still open (not completed, not
|
|
91
|
+
* blocked) while a different contract was proposed WITHOUT `supersedes`.
|
|
92
|
+
* Error: a different slice is never silently saved and ignored. To replace
|
|
93
|
+
* the active slice, add `round_contract.supersedes` naming it. */
|
|
99
94
|
export const CHECK_CONTRACT_PREMATURE = "contract_premature";
|
|
95
|
+
/** v4.0.6: A continuing round with contract capability and a successor
|
|
96
|
+
* available would leave the next round without an active contract. The final
|
|
97
|
+
* configured round has no successor consumer and is exempt. */
|
|
98
|
+
export const CHECK_CONTRACT_REQUIRED = "contract_required";
|
|
99
|
+
/** v4.0.6: A successor proposal repeats the closed contract's ACCEPTANCE ITEMS
|
|
100
|
+
* verbatim (only work_item/scope changed). Error: a successor must bring
|
|
101
|
+
* genuinely new acceptance conditions, or it would re-verify the old slice. */
|
|
102
|
+
export const CHECK_CONTRACT_SUCCESSOR_UNCHANGED = "contract_successor_unchanged";
|
|
100
103
|
/** v3.7.1: A cited gate (evaluation.gate_ids) is not approved. Opt-in:
|
|
101
104
|
* checked ONLY when policy.gate.enabled — citations are meaningless when
|
|
102
105
|
* the gate layer is off. Error → enforcement rejects the round. */
|
|
@@ -104,8 +107,6 @@ export const CHECK_USER_GATE_UNRESOLVED = "user_gate_unresolved";
|
|
|
104
107
|
export const CHECK_DOMAIN = {
|
|
105
108
|
// ── evaluation_consistency ────────────────────────────────────────────────
|
|
106
109
|
[CHECK_SUCCESS_WITH_REMAINING_CRITERIA]: "evaluation_consistency",
|
|
107
|
-
[CHECK_OUTCOME_SUCCESS_CONTRADICTION]: "evaluation_consistency",
|
|
108
|
-
[CHECK_BLOCKED_WITHOUT_BLOCKER]: "evaluation_consistency",
|
|
109
110
|
[CHECK_RETROACTIVE_CLAIM_BAD_ROUND]: "evaluation_consistency",
|
|
110
111
|
[CHECK_RETROACTIVE_CLAIM_UNVERIFIED]: "evaluation_consistency",
|
|
111
112
|
[CHECK_DUPLICATE_CONSTRAINT_DISCOVERY]: "evaluation_consistency",
|
|
@@ -116,17 +117,17 @@ export const CHECK_DOMAIN = {
|
|
|
116
117
|
[CHECK_CRITERIA_CLAIMS_UNVERIFIED]: "evidence_integrity",
|
|
117
118
|
[CHECK_EVIDENCE_INTEGRITY]: "evidence_integrity",
|
|
118
119
|
[CHECK_REQUIRED_COMMAND_FAILED]: "evidence_integrity",
|
|
119
|
-
[CHECK_COMMAND_EVIDENCE_MISMATCH]: "evidence_integrity",
|
|
120
120
|
[CHECK_VERIFICATION_ENTRYPOINT_MODIFIED]: "evidence_integrity",
|
|
121
121
|
[CHECK_TEST_FILES_MODIFIED]: "evidence_integrity",
|
|
122
122
|
// ── plan_contract ─────────────────────────────────────────────────────────
|
|
123
123
|
[CHECK_CONTRACT_ITEMS_UNVERIFIED]: "plan_contract",
|
|
124
124
|
[CHECK_ROUND_SCOPE_DRIFT]: "plan_contract",
|
|
125
125
|
[CHECK_CONTRACT_PREMATURE]: "plan_contract",
|
|
126
|
+
[CHECK_CONTRACT_REQUIRED]: "plan_contract",
|
|
127
|
+
[CHECK_CONTRACT_SUCCESSOR_UNCHANGED]: "plan_contract",
|
|
126
128
|
// v3.7.1: cited-gate authorization conformance (opt-in blocking layer).
|
|
127
129
|
[CHECK_USER_GATE_UNRESOLVED]: "plan_contract",
|
|
128
130
|
// ── progress_recovery ─────────────────────────────────────────────────────
|
|
129
|
-
[CHECK_BACKTRACK_WORKSPACE_NOT_RESTORED]: "progress_recovery",
|
|
130
131
|
};
|
|
131
132
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
132
133
|
// Helpers
|
|
@@ -206,21 +207,8 @@ function passedAfterCommand(deltas) {
|
|
|
206
207
|
* missing — the fix for the "weakest when it matters most" gap. */
|
|
207
208
|
export function deriveEvidenceStatus(selfEval, evidenceDeltas) {
|
|
208
209
|
const git = gitDeltaState(evidenceDeltas);
|
|
209
|
-
const gitObserved = git.state === "observed";
|
|
210
210
|
const command = passedAfterCommand(evidenceDeltas);
|
|
211
211
|
const commandVerified = command !== null;
|
|
212
|
-
// testsMachineBacked: agent-reported tests_reported agree with a passed
|
|
213
|
-
// command's parsed stdout (the same comparison checkCommandEvidenceIntegrity
|
|
214
|
-
// performs — parseTestOutput lives in this module).
|
|
215
|
-
let testsMachineBacked = false;
|
|
216
|
-
const reported = selfEval.execution_report?.tests_reported;
|
|
217
|
-
if (command && command.kind === "command" && reported) {
|
|
218
|
-
const parsed = parseTestOutput(command.data.stdoutExcerpt ?? "");
|
|
219
|
-
testsMachineBacked = parsed !== null &&
|
|
220
|
-
parsed.passed === reported.passed &&
|
|
221
|
-
parsed.failed === reported.failed &&
|
|
222
|
-
parsed.skipped === reported.skipped;
|
|
223
|
-
}
|
|
224
212
|
// v3.3: git observation alone is no longer "verified" — the machine seeing
|
|
225
213
|
// file changes is not the machine verifying success. Only a passed
|
|
226
214
|
// after-phase command (observed by the runtime itself) upgrades a round.
|
|
@@ -234,15 +222,15 @@ export function deriveEvidenceStatus(selfEval, evidenceDeltas) {
|
|
|
234
222
|
: "unavailable";
|
|
235
223
|
return {
|
|
236
224
|
providerStatus,
|
|
237
|
-
gitObserved,
|
|
238
225
|
gitChangeState: git.state,
|
|
239
226
|
commandVerified,
|
|
240
|
-
testsMachineBacked,
|
|
241
227
|
};
|
|
242
228
|
}
|
|
243
|
-
/**
|
|
244
|
-
|
|
245
|
-
return
|
|
229
|
+
/** Whether a changed path looks like a test file. */
|
|
230
|
+
function isTestFile(path) {
|
|
231
|
+
return /\.(test|spec)\.[a-z0-9]+$/i.test(path) ||
|
|
232
|
+
/(^|[\/])tests?[\/]/i.test(path) ||
|
|
233
|
+
/_test\.[a-z0-9]+$/i.test(path);
|
|
246
234
|
}
|
|
247
235
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
236
|
// Individual checks — each returns a VerificationFlag or null
|
|
@@ -255,9 +243,9 @@ function checkSuccessWithRemainingCriteria(selfEval) {
|
|
|
255
243
|
return null;
|
|
256
244
|
return makeVerificationFlag({
|
|
257
245
|
severity: "error",
|
|
258
|
-
field: "
|
|
246
|
+
field: "outcome",
|
|
259
247
|
check: CHECK_SUCCESS_WITH_REMAINING_CRITERIA,
|
|
260
|
-
detail: `
|
|
248
|
+
detail: `outcome=success but ${remaining.length} criteria remain unmet: ` +
|
|
261
249
|
remaining.slice(0, 3).join("; "),
|
|
262
250
|
});
|
|
263
251
|
}
|
|
@@ -266,55 +254,27 @@ function checkSuccessWithRemainingCriteria(selfEval) {
|
|
|
266
254
|
* command snapshot backs them. Warn — completion still counts, but the
|
|
267
255
|
* next prompt tells the agent its claims are unverified. This is the
|
|
268
256
|
* mild form of the 3.x criterion-specific completion rule. */
|
|
269
|
-
function checkUnverifiedCriteriaClaims(selfEval,
|
|
257
|
+
function checkUnverifiedCriteriaClaims(selfEval, machineVerifiedIds) {
|
|
270
258
|
const met = claimedMetCriteria(selfEval.execution_report);
|
|
271
259
|
if (met.length === 0)
|
|
272
260
|
return null;
|
|
273
|
-
|
|
261
|
+
// v4.0.3: EVERY met claim is checked. A verified claim elsewhere in the
|
|
262
|
+
// same round does not excuse an unbacked one.
|
|
263
|
+
const unverified = met.filter((target) => {
|
|
264
|
+
const id = isCriterionId(target) ? target : deriveCriterionId(target);
|
|
265
|
+
return !machineVerifiedIds.has(id);
|
|
266
|
+
});
|
|
267
|
+
if (unverified.length === 0)
|
|
274
268
|
return null;
|
|
275
269
|
return makeVerificationFlag({
|
|
276
270
|
severity: "warn",
|
|
277
271
|
field: "execution_report",
|
|
278
272
|
check: CHECK_CRITERIA_CLAIMS_UNVERIFIED,
|
|
279
|
-
detail: `${met.length} criteria reported met
|
|
280
|
-
"
|
|
273
|
+
detail: `${unverified.length} of ${met.length} criteria reported met are not ` +
|
|
274
|
+
"machine-verified: " + unverified.slice(0, 3).join("; ") +
|
|
275
|
+
(unverified.length > 3 ? " …" : ""),
|
|
281
276
|
});
|
|
282
277
|
}
|
|
283
|
-
/** Declared outcome vs the core success flag — one check id for the whole
|
|
284
|
-
* axis (v3.7: the former success_claim_conflict warn direction merged here).
|
|
285
|
-
* A declared success with success=false is a self-contradiction (error);
|
|
286
|
-
* success=true with a non-success outcome is an inconsistent claim (warn).
|
|
287
|
-
* Also: outcome==="blocked" without a blocker description gets a warn so the
|
|
288
|
-
* next prompt asks for it. */
|
|
289
|
-
function checkOutcomeConsistency(selfEval) {
|
|
290
|
-
const outcome = selfEval.outcome;
|
|
291
|
-
if (outcome === "success" && selfEval.success === false) {
|
|
292
|
-
return makeVerificationFlag({
|
|
293
|
-
severity: "error",
|
|
294
|
-
field: "outcome",
|
|
295
|
-
check: CHECK_OUTCOME_SUCCESS_CONTRADICTION,
|
|
296
|
-
detail: "outcome=success but success=false — self-contradictory",
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
if (outcome !== undefined && outcome !== "success" && selfEval.success === true) {
|
|
300
|
-
return makeVerificationFlag({
|
|
301
|
-
severity: "warn",
|
|
302
|
-
field: "success",
|
|
303
|
-
check: CHECK_OUTCOME_SUCCESS_CONTRADICTION,
|
|
304
|
-
detail: `outcome=${outcome} but success=true — the declared outcome wins`,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
if (outcome === "blocked" &&
|
|
308
|
-
(typeof selfEval.blocker !== "string" || selfEval.blocker.trim().length === 0)) {
|
|
309
|
-
return makeVerificationFlag({
|
|
310
|
-
severity: "warn",
|
|
311
|
-
field: "blocker",
|
|
312
|
-
check: CHECK_BLOCKED_WITHOUT_BLOCKER,
|
|
313
|
-
detail: "outcome=blocked but no blocker description was provided",
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
278
|
/** v2.12: Retroactive claims reference PRIOR rounds. Each claim's file
|
|
319
279
|
* paths are checked against the referenced round's committed git evidence
|
|
320
280
|
* (P0 provenance layer). Unverifiable claims warn — they never upgrade
|
|
@@ -366,11 +326,10 @@ function checkRetroactiveClaims(selfEval, vaultEntries, currentRound) {
|
|
|
366
326
|
}
|
|
367
327
|
/** v2.12/v3.6/v3.7/v3.9.1: success without machine backing — the single
|
|
368
328
|
* "success evidence" semantic. Two arms, evaluated in order:
|
|
369
|
-
* 1.
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* success out of the success trajectory.
|
|
329
|
+
* 1. Missing-report arm: outcome=success with NO execution_report at all is
|
|
330
|
+
* a WARN. It is a reporting gap, not an acceptance gate: the runtime does
|
|
331
|
+
* not reject a round because an optional report field was not filled. An
|
|
332
|
+
* empty files_changed is a legitimate no-change report, not a gap.
|
|
374
333
|
* 2. Claims arm (v3.6): success=true but the runtime observed no machine
|
|
375
334
|
* evidence for it. The trigger is the runtime-derived providerStatus
|
|
376
335
|
* (verified / unavailable / absent) — "verified" requires an UNTAMPERED
|
|
@@ -394,17 +353,14 @@ function checkSuccessWithoutVerifiedEvidence(selfEval, status) {
|
|
|
394
353
|
// remains the verification fact, and the warn still keeps success out of
|
|
395
354
|
// the success trajectory (shouldPushSuccess keys on this check).
|
|
396
355
|
const ev = selfEval.execution_report;
|
|
397
|
-
if (!ev
|
|
356
|
+
if (!ev) {
|
|
398
357
|
return makeVerificationFlag({
|
|
399
358
|
severity: "warn",
|
|
400
|
-
field: "
|
|
359
|
+
field: "outcome",
|
|
401
360
|
check: CHECK_SUCCESS_WITHOUT_VERIFIED_EVIDENCE,
|
|
402
|
-
detail:
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
: "Agent claims success but provided no execution_report. The report is " +
|
|
406
|
-
"expected but missing; this is a reporting gap, not by itself a " +
|
|
407
|
-
"rejection. No machine evidence backs the claim.",
|
|
361
|
+
detail: "outcome=success but no execution_report was provided. The report " +
|
|
362
|
+
"is expected but missing; this is a reporting gap, not by itself a " +
|
|
363
|
+
"rejection. No machine evidence backs the claim.",
|
|
408
364
|
});
|
|
409
365
|
}
|
|
410
366
|
// Claims arm (v3.6).
|
|
@@ -427,12 +383,12 @@ function checkSuccessWithoutVerifiedEvidence(selfEval, status) {
|
|
|
427
383
|
// this warn). The bounded verification-debt row is what eventually acts on
|
|
428
384
|
// a persistent pattern. `no_change_reason` stays info, and only works when
|
|
429
385
|
// machine verification was structurally impossible (no enabled command).
|
|
430
|
-
// A success claim with NO execution_report at all is the other arm and
|
|
431
|
-
//
|
|
386
|
+
// A success claim with NO execution_report at all is the other arm and is
|
|
387
|
+
// also a warn: report coverage never decides acceptance.
|
|
432
388
|
const severity = noChange ? "info" : "warn";
|
|
433
389
|
return makeVerificationFlag({
|
|
434
390
|
severity,
|
|
435
|
-
field: "
|
|
391
|
+
field: "outcome",
|
|
436
392
|
check: CHECK_SUCCESS_WITHOUT_VERIFIED_EVIDENCE,
|
|
437
393
|
detail: noChange
|
|
438
394
|
? "success with zero verified claims (no_change_reason declared)"
|
|
@@ -446,18 +402,18 @@ function checkDuplicateConstraintDiscovery(selfEval, prevSelfEval, olderViolatio
|
|
|
446
402
|
// Collect all previously-known constraints
|
|
447
403
|
const known = new Set();
|
|
448
404
|
for (const v of olderViolations)
|
|
449
|
-
known.add(v
|
|
405
|
+
known.add(normalizeText(v));
|
|
450
406
|
if (prevSelfEval) {
|
|
451
407
|
for (const d of prevSelfEval.discovered_constraints ?? []) {
|
|
452
|
-
known.add(d
|
|
408
|
+
known.add(normalizeText(d));
|
|
453
409
|
}
|
|
454
410
|
// Also treat previous violations as implicitly discovered
|
|
455
411
|
for (const v of prevSelfEval.constraint_violations) {
|
|
456
|
-
known.add(v
|
|
412
|
+
known.add(normalizeText(v));
|
|
457
413
|
}
|
|
458
414
|
}
|
|
459
415
|
for (const d of discovered) {
|
|
460
|
-
if (known.has(d
|
|
416
|
+
if (known.has(normalizeText(d))) {
|
|
461
417
|
return makeVerificationFlag({
|
|
462
418
|
severity: "warn",
|
|
463
419
|
field: "discovered_constraints",
|
|
@@ -468,29 +424,27 @@ function checkDuplicateConstraintDiscovery(selfEval, prevSelfEval, olderViolatio
|
|
|
468
424
|
}
|
|
469
425
|
return null;
|
|
470
426
|
}
|
|
471
|
-
function checkRecurringViolation(selfEval, prevSelfEval,
|
|
427
|
+
function checkRecurringViolation(selfEval, prevSelfEval, committedRounds, currentRound) {
|
|
472
428
|
const currViols = selfEval.constraint_violations;
|
|
473
429
|
if (!currViols || currViols.length === 0)
|
|
474
430
|
return null;
|
|
475
431
|
// Build the violation history: [round N-2, round N-1, round N]
|
|
476
432
|
const violationsByRound = [];
|
|
477
|
-
// Round N-2 violations come from the
|
|
478
|
-
// (persistLoopLineage stores the PREVIOUS round's violations on each entry)
|
|
433
|
+
// Round N-2 violations come from the committed round's own evaluation.
|
|
479
434
|
if (currentRound >= 3) {
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
violationsByRound.push(viols.map((v) => v.toLowerCase().trim()));
|
|
435
|
+
const nMinus2 = committedRounds.find((view) => view.round === currentRound - 2);
|
|
436
|
+
const viols = nMinus2?.constraintViolations ?? [];
|
|
437
|
+
if (viols.length) {
|
|
438
|
+
violationsByRound.push(viols.map((v) => normalizeText(v)));
|
|
485
439
|
}
|
|
486
440
|
}
|
|
487
441
|
// Round N-1 violations from prevSelfEval
|
|
488
442
|
if (prevSelfEval) {
|
|
489
443
|
const prevViols = prevSelfEval.constraint_violations;
|
|
490
|
-
violationsByRound.push(prevViols.map((v) => v
|
|
444
|
+
violationsByRound.push(prevViols.map((v) => normalizeText(v)));
|
|
491
445
|
}
|
|
492
446
|
// Round N violations from current selfEval
|
|
493
|
-
violationsByRound.push(currViols.map((v) => v
|
|
447
|
+
violationsByRound.push(currViols.map((v) => normalizeText(v)));
|
|
494
448
|
// Need at least 3 rounds of data
|
|
495
449
|
if (violationsByRound.length < 3)
|
|
496
450
|
return null;
|
|
@@ -515,12 +469,11 @@ function checkRetractFreshConstraint(selfEval, prevSelfEval, currentRound) {
|
|
|
515
469
|
return null;
|
|
516
470
|
if (!prevSelfEval)
|
|
517
471
|
return null;
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
472
|
+
const discoveries = prevSelfEval.discovered_constraints ?? [];
|
|
473
|
+
const lastRoundDiscoveries = new Set(discoveries.map((discovery) => normalizeText(discovery)));
|
|
474
|
+
const discoveryIds = new Set(discoveries.map((discovery) => deriveConstraintId(discovery)));
|
|
522
475
|
for (const r of retracted) {
|
|
523
|
-
if (lastRoundDiscoveries.has(r
|
|
476
|
+
if (lastRoundDiscoveries.has(normalizeText(r)) || discoveryIds.has(r)) {
|
|
524
477
|
return makeVerificationFlag({
|
|
525
478
|
severity: "warn",
|
|
526
479
|
field: "retracted_constraints",
|
|
@@ -534,10 +487,12 @@ function checkRetractFreshConstraint(selfEval, prevSelfEval, currentRound) {
|
|
|
534
487
|
}
|
|
535
488
|
/** v1.18: Cross-validate agent-reported files_changed against git evidence
|
|
536
489
|
* from the configured evidence providers (MachineObservation array). */
|
|
537
|
-
function checkEvidenceIntegrity(selfEval, evidenceDeltas
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
490
|
+
function checkEvidenceIntegrity(selfEval, evidenceDeltas,
|
|
491
|
+
/** v3.9.4: whether the raw submission named
|
|
492
|
+
* `execution_report.files_changed`. The receipt uses the SAME manifest, so
|
|
493
|
+
* the two projections can never disagree about an omitted report. */
|
|
494
|
+
filesReported) {
|
|
495
|
+
const reconciliation = deriveFilesReconciliation(selfEval.execution_report ?? null, evidenceDeltas, { attempted: filesReported });
|
|
541
496
|
if (reconciliation.status !== "mismatch")
|
|
542
497
|
return null;
|
|
543
498
|
const parts = [];
|
|
@@ -576,124 +531,7 @@ function checkRequiredCommandEvidence(selfEval, evidenceDeltas) {
|
|
|
576
531
|
field: "execution_report",
|
|
577
532
|
check: CHECK_REQUIRED_COMMAND_FAILED,
|
|
578
533
|
detail: `Agent claims success but required command "${name}" ${String(status)}${exitCode}`,
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
// ── Test output parsing ────────────────────────────────────────────────────
|
|
584
|
-
/** Whether a changed path looks like a test file. */
|
|
585
|
-
function isTestFile(path) {
|
|
586
|
-
return /\.(test|spec)\.[a-z0-9]+$/i.test(path) ||
|
|
587
|
-
/(^|[\\/])tests?[\\/]/i.test(path) ||
|
|
588
|
-
/_test\.[a-z0-9]+$/i.test(path);
|
|
589
|
-
}
|
|
590
|
-
/** Best-effort parse of test counts from common test-runner output formats.
|
|
591
|
-
*
|
|
592
|
-
* Scans the last 2000 characters of stdout (where summary lines typically
|
|
593
|
-
* appear) and tries patterns in descending specificity order. Returns null
|
|
594
|
-
* when the output format is unrecognized, stdout is empty, or a confident
|
|
595
|
-
* parse cannot be made.
|
|
596
|
-
*
|
|
597
|
-
* Recognized formats: Jest verbose/compact, Mocha, pytest/unittest, Go test,
|
|
598
|
-
* and PHPUnit OK summaries. */
|
|
599
|
-
export function parseTestOutput(stdout) {
|
|
600
|
-
if (!stdout)
|
|
601
|
-
return null;
|
|
602
|
-
const tail = stdout.length > 2000 ? stdout.slice(-2000) : stdout;
|
|
603
|
-
let match;
|
|
604
|
-
// 1. Jest verbose: "Tests: 1 failed, 8 passed, 9 total"
|
|
605
|
-
match = tail.match(/Tests:\s*(\d+)\s+failed,\s*(\d+)\s+passed,\s*(\d+)\s+total/i);
|
|
606
|
-
if (match) {
|
|
607
|
-
return { failed: Number(match[1]), passed: Number(match[2]), total: Number(match[3]), skipped: 0 };
|
|
608
|
-
}
|
|
609
|
-
// 2. Jest compact: "Tests: 8 passed, 9 total"
|
|
610
|
-
match = tail.match(/Tests:\s*(\d+)\s+passed,\s*(\d+)\s+total/i);
|
|
611
|
-
if (match) {
|
|
612
|
-
const passed = Number(match[1]);
|
|
613
|
-
const total = Number(match[2]);
|
|
614
|
-
return { passed, failed: total - passed, total, skipped: 0 };
|
|
615
|
-
}
|
|
616
|
-
// 3. Mocha: "8 passing" + optional "2 failing"
|
|
617
|
-
const mochaPass = tail.match(/(\d+)\s+passing/i);
|
|
618
|
-
if (mochaPass) {
|
|
619
|
-
const mochaFail = tail.match(/(\d+)\s+failing/i);
|
|
620
|
-
const passed = Number(mochaPass[1]);
|
|
621
|
-
const failed = mochaFail ? Number(mochaFail[1]) : 0;
|
|
622
|
-
return { passed, failed, total: passed + failed, skipped: 0 };
|
|
623
|
-
}
|
|
624
|
-
// 4. Go test: count "--- PASS:" and "--- FAIL:" lines
|
|
625
|
-
const goPassCount = (tail.match(/---\s+PASS:/g) || []).length;
|
|
626
|
-
const goFailCount = (tail.match(/---\s+FAIL:/g) || []).length;
|
|
627
|
-
if (goPassCount > 0 || goFailCount > 0) {
|
|
628
|
-
return { passed: goPassCount, failed: goFailCount, total: goPassCount + goFailCount, skipped: 0 };
|
|
629
|
-
}
|
|
630
|
-
// 5. pytest / unittest: "8 passed, 1 failed" or "8 passed"
|
|
631
|
-
match = tail.match(/(\d+)\s+passed[,;\s]+(\d+)\s+failed/i);
|
|
632
|
-
if (match) {
|
|
633
|
-
return { passed: Number(match[1]), failed: Number(match[2]), total: Number(match[1]) + Number(match[2]), skipped: 0 };
|
|
634
|
-
}
|
|
635
|
-
// pytest compact: "8 passed" (with a reasonable context hint)
|
|
636
|
-
match = tail.match(/(?:=\s+test session|\b(?:passed|failed)\b)/i);
|
|
637
|
-
if (match) {
|
|
638
|
-
const pCount = tail.match(/(\d+)\s+passed/i);
|
|
639
|
-
const fCount = tail.match(/(\d+)\s+failed/i);
|
|
640
|
-
if (pCount || fCount) {
|
|
641
|
-
const passed = pCount ? Number(pCount[1]) : 0;
|
|
642
|
-
const failed = fCount ? Number(fCount[1]) : 0;
|
|
643
|
-
return { passed, failed, total: passed + failed, skipped: 0 };
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// 6. PHPUnit: "OK (8 tests, 16 assertions)"
|
|
647
|
-
match = tail.match(/OK\s*\((\d+)\s+tests?/i);
|
|
648
|
-
if (match) {
|
|
649
|
-
const total = Number(match[1]);
|
|
650
|
-
return { passed: total, failed: 0, total, skipped: 0 };
|
|
651
|
-
}
|
|
652
|
-
return null;
|
|
653
|
-
}
|
|
654
|
-
/** Cross-validate agent-reported tests_reported against command evidence output.
|
|
655
|
-
*
|
|
656
|
-
* For each command provider whose status is "passed" and output is not
|
|
657
|
-
* truncated, attempts to parse test counts from stdout. Mismatched counts
|
|
658
|
-
* produce a warn-level flag; failures hidden by the agent (reported 0 failed
|
|
659
|
-
* when the command shows >0) produce an error-level flag.
|
|
660
|
-
*
|
|
661
|
-
* Non-command evidence providers are skipped (no structural cross-check is
|
|
662
|
-
* defined for them yet). */
|
|
663
|
-
function checkCommandEvidenceIntegrity(selfEval, evidenceDeltas) {
|
|
664
|
-
const reported = selfEval.execution_report?.tests_reported;
|
|
665
|
-
if (!reported)
|
|
666
|
-
return null;
|
|
667
|
-
for (const snapshot of afterObservations(evidenceDeltas)) {
|
|
668
|
-
if (snapshot.kind !== "command")
|
|
669
|
-
continue;
|
|
670
|
-
if (snapshot.status !== "passed")
|
|
671
|
-
continue;
|
|
672
|
-
if (snapshot.data.truncated === true)
|
|
673
|
-
continue;
|
|
674
|
-
const stdout = typeof snapshot.data.stdoutExcerpt === "string" ? snapshot.data.stdoutExcerpt : "";
|
|
675
|
-
const parsed = parseTestOutput(stdout);
|
|
676
|
-
if (!parsed)
|
|
677
|
-
continue;
|
|
678
|
-
const mismatches = [];
|
|
679
|
-
if (parsed.passed !== reported.passed) {
|
|
680
|
-
mismatches.push(`passed: reported ${reported.passed}, command shows ${parsed.passed}`);
|
|
681
|
-
}
|
|
682
|
-
if (parsed.failed !== reported.failed) {
|
|
683
|
-
mismatches.push(`failed: reported ${reported.failed}, command shows ${parsed.failed}`);
|
|
684
|
-
}
|
|
685
|
-
if (mismatches.length === 0) {
|
|
686
|
-
// Counts match exactly — evidence supports the agent's claim.
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
689
|
-
const name = snapshot.data.commandId;
|
|
690
|
-
// Agent hides failures → error. Other mismatch → warn.
|
|
691
|
-
const severity = parsed.failed > 0 && reported.failed === 0 ? "error" : "warn";
|
|
692
|
-
return makeVerificationFlag({
|
|
693
|
-
severity,
|
|
694
|
-
field: "tests_reported",
|
|
695
|
-
check: CHECK_COMMAND_EVIDENCE_MISMATCH,
|
|
696
|
-
detail: `Agent tests_reported don't match "${name}" output: ${mismatches.join("; ")}`,
|
|
534
|
+
ref: name,
|
|
697
535
|
});
|
|
698
536
|
}
|
|
699
537
|
return null;
|
|
@@ -741,10 +579,9 @@ function checkVerificationDomainIntegrity(evidenceDeltas, drift) {
|
|
|
741
579
|
(changed.length > 0
|
|
742
580
|
? "At round start: " + changed.map((entry) => entry.roundStart === "absent"
|
|
743
581
|
? `"${entry.file}" did not exist`
|
|
744
|
-
: `"${entry.file}" was present`).join("; ") + ".
|
|
745
|
-
: "")
|
|
746
|
-
|
|
747
|
-
"entrypoint unchanged.",
|
|
582
|
+
: `"${entry.file}" was present`).join("; ") + "."
|
|
583
|
+
: ""),
|
|
584
|
+
ref: snapshot.data.commandId,
|
|
748
585
|
});
|
|
749
586
|
}
|
|
750
587
|
// Only when NO entrypoint was tampered does the test-file warn stand in.
|
|
@@ -761,136 +598,11 @@ function checkVerificationDomainIntegrity(evidenceDeltas, drift) {
|
|
|
761
598
|
check: CHECK_TEST_FILES_MODIFIED,
|
|
762
599
|
detail: `Test files changed in the same round command "${snapshot.data.commandId}" passed — ` +
|
|
763
600
|
"confirm the tests still verify the task claims.",
|
|
601
|
+
ref: snapshot.data.commandId,
|
|
764
602
|
});
|
|
765
603
|
}
|
|
766
604
|
return null;
|
|
767
605
|
}
|
|
768
|
-
/** v2.13: After a backtrack, check that the agent restored the workspace
|
|
769
|
-
* before working. Two arms:
|
|
770
|
-
*
|
|
771
|
-
* 1. Machine arm (M3, error): a skipped file whose CURRENT git fingerprint
|
|
772
|
-
* still equals the fingerprint recorded at its failed round was never
|
|
773
|
-
* touched since the rollback — the workspace was provably not restored.
|
|
774
|
-
* Restored or re-modified files change fingerprint and can never be
|
|
775
|
-
* misjudged by this arm.
|
|
776
|
-
*
|
|
777
|
-
* 2. Claims arm (warn only): the agent's files_changed overlaps the skipped
|
|
778
|
-
* list. A self-report alone cannot distinguish a restored-then-redone
|
|
779
|
-
* file from a never-restored one — a legitimate multi-file redo would
|
|
780
|
-
* otherwise be falsely terminated — so overlap is guidance, not a
|
|
781
|
-
* verdict. (The old error tier for >= 3 overlapping files was removed
|
|
782
|
-
* for exactly this reason: it fired on honest redos.) */
|
|
783
|
-
function checkBacktrackWorkspaceRestore(selfEval, backtrackSkippedFiles, backtrackSkippedFingerprints = {}, evidenceDeltas = []) {
|
|
784
|
-
// Machine arm — untouched-since-rollback proof, independent of claims.
|
|
785
|
-
const failedEntries = Object.entries(backtrackSkippedFingerprints);
|
|
786
|
-
if (failedEntries.length > 0) {
|
|
787
|
-
const afterGit = evidenceDeltas.find((delta) => delta.kind === "git")?.after;
|
|
788
|
-
const currentFingerprints = afterGit && afterGit.kind === "git" &&
|
|
789
|
-
isRecord(afterGit.data.fingerprints)
|
|
790
|
-
? afterGit.data.fingerprints
|
|
791
|
-
: null;
|
|
792
|
-
if (currentFingerprints) {
|
|
793
|
-
const untouched = failedEntries
|
|
794
|
-
.filter(([file, failedFp]) => currentFingerprints[file] === failedFp)
|
|
795
|
-
.map(([file]) => file)
|
|
796
|
-
.sort();
|
|
797
|
-
if (untouched.length > 0) {
|
|
798
|
-
return makeVerificationFlag({
|
|
799
|
-
severity: "error",
|
|
800
|
-
field: "workspace",
|
|
801
|
-
check: CHECK_BACKTRACK_WORKSPACE_NOT_RESTORED,
|
|
802
|
-
detail: `File(s) from the failed rounds are byte-identical to their ` +
|
|
803
|
-
`state at the rollback: ${untouched.slice(0, 5).join(", ")}. The ` +
|
|
804
|
-
`workspace was never restored — restore it to the restore point ` +
|
|
805
|
-
`(git checkout/reset) before redoing the work.`,
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
const ev = selfEval.execution_report;
|
|
811
|
-
if (!ev || (ev.files_changed ?? []).length === 0)
|
|
812
|
-
return null;
|
|
813
|
-
// Claims arm — overlap as guidance only.
|
|
814
|
-
const overlap = (ev.files_changed ?? []).filter((f) => backtrackSkippedFiles.some((sf) => sf === f || f.endsWith(sf) || sf.endsWith(f)));
|
|
815
|
-
if (overlap.length === 0)
|
|
816
|
-
return null;
|
|
817
|
-
return makeVerificationFlag({
|
|
818
|
-
severity: "warn",
|
|
819
|
-
field: "files_changed",
|
|
820
|
-
check: CHECK_BACKTRACK_WORKSPACE_NOT_RESTORED,
|
|
821
|
-
detail: `Agent modified files that overlap with skipped rounds: ` +
|
|
822
|
-
`${overlap.join(", ")}. Verify the workspace was restored.`,
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
/** v2.12: After a backtrack, the workspace must return to the restore point:
|
|
826
|
-
* the same git HEAD that round committed, carrying no dirty file the restore
|
|
827
|
-
* point did not already have. Compares HEAD's first 12 characters (short
|
|
828
|
-
* hash), matching how git itself disambiguates.
|
|
829
|
-
*
|
|
830
|
-
* The dirty-set comparison is against the restore point's OWN baseline, not
|
|
831
|
-
* an empty tree: unrelated work in progress in the same workspace is no
|
|
832
|
-
* evidence of a failed restore, while a file the failed rounds left behind
|
|
833
|
-
* is — it is dirty now and was not dirty then. An absent baseline means the
|
|
834
|
-
* restore point's dirt was never observed, so only HEAD can be judged.
|
|
835
|
-
*
|
|
836
|
-
* A redo prepared before the agent restores the workspace has a stale before
|
|
837
|
-
* snapshot, so its first submission is a recovery checkpoint; once the
|
|
838
|
-
* restore is proven, later work may move HEAD freely. */
|
|
839
|
-
function checkBacktrackGitHeadRestore(evidenceDeltas, backtrackTargetGitHead, beforeEvidence = [],
|
|
840
|
-
/** The restore point's own dirty files — undefined when never observed. */
|
|
841
|
-
backtrackTargetDirty) {
|
|
842
|
-
if (!backtrackTargetGitHead)
|
|
843
|
-
return null;
|
|
844
|
-
const short = (hash) => hash.slice(0, 12);
|
|
845
|
-
const readGit = (observations) => {
|
|
846
|
-
const snapshot = observations.find((item) => item.providerId === "git" && item.status === "observed" && isRecord(item.data) &&
|
|
847
|
-
typeof item.data.head === "string");
|
|
848
|
-
if (!snapshot || !isRecord(snapshot.data))
|
|
849
|
-
return null;
|
|
850
|
-
const data = snapshot.data;
|
|
851
|
-
// v3.9.1: the workspace's dirty set is one explicit list. "Tracked /
|
|
852
|
-
// staged / untracked" were dirty-since-HEAD categories, not the complete
|
|
853
|
-
// boundary, and are gone.
|
|
854
|
-
const dirty = Array.isArray(data.dirty)
|
|
855
|
-
? data.dirty.filter((file) => typeof file === "string")
|
|
856
|
-
: [];
|
|
857
|
-
return { head: data.head, dirty };
|
|
858
|
-
};
|
|
859
|
-
const foreignDirty = (dirty) => Array.isArray(backtrackTargetDirty)
|
|
860
|
-
? dirty.filter((file) => !backtrackTargetDirty.includes(file))
|
|
861
|
-
: [];
|
|
862
|
-
const restored = (git) => git !== null && git.head.length > 0 &&
|
|
863
|
-
short(git.head) === short(backtrackTargetGitHead) &&
|
|
864
|
-
foreignDirty(git.dirty).length === 0;
|
|
865
|
-
// When restoration happened before the redo was prepared, the before
|
|
866
|
-
// snapshot already proves it. The redo may then commit new work.
|
|
867
|
-
if (restored(readGit(beforeEvidence)))
|
|
868
|
-
return null;
|
|
869
|
-
const afterGit = readGit(afterObservations(evidenceDeltas));
|
|
870
|
-
if (!afterGit)
|
|
871
|
-
return null; // git unavailable → cannot verify → skip (fail-open)
|
|
872
|
-
if (restored(afterGit))
|
|
873
|
-
return null;
|
|
874
|
-
// Normal lifecycle: prepare captured the pre-restore workspace. The first
|
|
875
|
-
// submission must prove restoration before a later submission can commit.
|
|
876
|
-
const headBack = afterGit.head.length > 0 &&
|
|
877
|
-
short(afterGit.head) === short(backtrackTargetGitHead);
|
|
878
|
-
const left = foreignDirty(afterGit.dirty);
|
|
879
|
-
return makeVerificationFlag({
|
|
880
|
-
severity: "error",
|
|
881
|
-
field: "workspace",
|
|
882
|
-
check: CHECK_BACKTRACK_WORKSPACE_NOT_RESTORED,
|
|
883
|
-
detail: headBack
|
|
884
|
-
? `Workspace HEAD is back at the backtrack restore commit ` +
|
|
885
|
-
`(${short(backtrackTargetGitHead)}) but these files were not there at ` +
|
|
886
|
-
`the restore point and are still changed: ${left.slice(0, 5).join(", ")}` +
|
|
887
|
-
`${left.length > 5 ? ` (+${left.length - 5} more)` : ""}. Revert them ` +
|
|
888
|
-
`before redoing the work.`
|
|
889
|
-
: `Workspace HEAD (${short(afterGit.head)}) does not match the backtrack ` +
|
|
890
|
-
`restore commit (${short(backtrackTargetGitHead)}). Submit one recovery ` +
|
|
891
|
-
`evaluation at the restore point before redoing work.`,
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
606
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
895
607
|
// v3.8 — Round Contract checks (all silent when no contract is declared)
|
|
896
608
|
//
|
|
@@ -930,58 +642,132 @@ function checkRoundScopeDrift(activeContract, evidenceDeltas) {
|
|
|
930
642
|
if (outOfScope === null || outOfScope.length === 0)
|
|
931
643
|
return null;
|
|
932
644
|
return makeVerificationFlag({
|
|
933
|
-
|
|
645
|
+
// v3.9.9: report only. `scope` is the Agent's declared intent, not a
|
|
646
|
+
// permission, so drift is an INFO fact — it never rejects, terminates,
|
|
647
|
+
// backtracks, stalls, or counts toward a rejection streak.
|
|
648
|
+
severity: "info",
|
|
934
649
|
field: "round_contract",
|
|
935
650
|
check: CHECK_ROUND_SCOPE_DRIFT,
|
|
936
|
-
detail: `Files changed outside the active contract's declared scope` +
|
|
651
|
+
detail: `Files changed outside the active contract's declared scope (report ` +
|
|
652
|
+
`only; this does not block the round)` +
|
|
937
653
|
(outOfScope.length > 5
|
|
938
654
|
? ` (${outOfScope.length} total, showing 5): ${outOfScope.slice(0, 5).join(", ")}`
|
|
939
|
-
: `: ${outOfScope.join(", ")}`)
|
|
940
|
-
`. Revert them, or close the active contract and declare the extended scope in a new proposal.`,
|
|
655
|
+
: `: ${outOfScope.join(", ")}`),
|
|
941
656
|
});
|
|
942
657
|
}
|
|
943
658
|
/** v3.8: Claimed-but-unverified contract items. The round COMMITS — the debt
|
|
944
659
|
* is surfaced (prompt, state file, handoff) and the enforcement gate's
|
|
945
660
|
* verification-debt row handles a persistent pattern. */
|
|
946
|
-
function checkContractItemsUnverified(statuses) {
|
|
661
|
+
function checkContractItemsUnverified(statuses, currentReport) {
|
|
947
662
|
if (statuses.insufficientCount === 0)
|
|
948
663
|
return null;
|
|
664
|
+
// v3.9.9: an honest abandonment retires the old debt finding. A superseded
|
|
665
|
+
// contract whose round does NOT claim any of its items `met` is the Agent
|
|
666
|
+
// saying "this slice was wrong" — not a completion claim to police. If the
|
|
667
|
+
// round still claims an old item `met`, the debt finding stands: a redirect
|
|
668
|
+
// cannot wash away an unverified completion claim.
|
|
669
|
+
if (statuses.closure === "superseded") {
|
|
670
|
+
const itemIds = new Set(statuses.items.map((item) => item.itemId));
|
|
671
|
+
const claimsMet = (currentReport?.claims ?? []).some((claim) => claim.id.startsWith("rci-") &&
|
|
672
|
+
claim.outcome === "met" && itemIds.has(claim.id));
|
|
673
|
+
if (!claimsMet)
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
// v3.9.4: EVERY insufficient item is named. The previous 3-item cap hid the
|
|
677
|
+
// rest of the debt from the one repair surface the agent reads.
|
|
949
678
|
const unverified = statuses.items
|
|
950
679
|
.filter((item) => item.status === "insufficient")
|
|
951
|
-
.slice(0, 3)
|
|
952
680
|
.map((item) => item.itemId);
|
|
953
681
|
return makeVerificationFlag({
|
|
954
682
|
severity: "warn",
|
|
955
683
|
field: "round_contract",
|
|
956
684
|
check: CHECK_CONTRACT_ITEMS_UNVERIFIED,
|
|
957
685
|
detail: `${statuses.insufficientCount} contract item(s) claimed met but not machine-verified: ` +
|
|
958
|
-
`${unverified.join(", ")}
|
|
686
|
+
`${unverified.join(", ")}.`,
|
|
959
687
|
});
|
|
960
688
|
}
|
|
961
|
-
/** A different contract proposed while the ACTIVE contract is still
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
* proposal is
|
|
689
|
+
/** v4.0.6: A different contract proposed while the ACTIVE contract is still
|
|
690
|
+
* open and the proposal does not explicitly `supersedes` it. This is an
|
|
691
|
+
* ERROR: a different slice is never silently saved and ignored. Silent when
|
|
692
|
+
* the active contract is closed (all items verified, or outcome=blocked),
|
|
693
|
+
* when no proposal is submitted, or when the proposal is a restate (content
|
|
694
|
+
* equality, never id equality). A legal redirect makes the closure
|
|
695
|
+
* `superseded`, so it never reaches this check. */
|
|
966
696
|
function checkContractPremature(activeContract, statuses, selfEval) {
|
|
967
697
|
if (!activeContract)
|
|
968
698
|
return null;
|
|
969
699
|
if (statuses.closure !== "open")
|
|
970
700
|
return null; // closed
|
|
971
|
-
if (selfEval
|
|
701
|
+
if (declaresBlocked(selfEval))
|
|
972
702
|
return null; // closed
|
|
973
703
|
const proposal = selfEval.round_contract;
|
|
974
704
|
if (!proposal)
|
|
975
705
|
return null;
|
|
706
|
+
if (proposal.supersedes?.contract_id === activeContract.id)
|
|
707
|
+
return null; // explicit redirect
|
|
976
708
|
if (sameContract(proposal, activeContract))
|
|
977
709
|
return null; // restate → continue
|
|
978
710
|
return makeVerificationFlag({
|
|
979
|
-
severity: "
|
|
711
|
+
severity: "error",
|
|
980
712
|
field: "round_contract",
|
|
981
713
|
check: CHECK_CONTRACT_PREMATURE,
|
|
982
714
|
detail: "The ACTIVE Round Contract is still open (not every item verified, not blocked) " +
|
|
983
|
-
"while a different contract was proposed
|
|
984
|
-
"
|
|
715
|
+
"while a different contract was proposed without supersedes — to replace it, " +
|
|
716
|
+
"add round_contract.supersedes with the active contract id and different " +
|
|
717
|
+
"acceptance items; otherwise omit round_contract to continue.",
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
/** v4.0.6: contract coverage. With an after-capable command configured, a
|
|
721
|
+
* continuing round must leave the next round with an active contract.
|
|
722
|
+
* `blocked` rounds are exempt (they close the contract and stop). The rule is
|
|
723
|
+
* the shared `deriveContractRequirement`, so the submission boundary and the
|
|
724
|
+
* gate cannot disagree. */
|
|
725
|
+
function checkContractRequired(activeContract, statuses, selfEval, contractCapability, successorRoundAvailable) {
|
|
726
|
+
const requirement = deriveContractRequirement({
|
|
727
|
+
shouldContinue: selfEval.should_continue === true,
|
|
728
|
+
blocked: declaresBlocked(selfEval),
|
|
729
|
+
proposalPresent: selfEval.round_contract !== undefined,
|
|
730
|
+
activeContract,
|
|
731
|
+
closure: statuses.closure,
|
|
732
|
+
contractCapability,
|
|
733
|
+
successorRoundAvailable,
|
|
734
|
+
});
|
|
735
|
+
if (!requirement.required)
|
|
736
|
+
return null;
|
|
737
|
+
return makeVerificationFlag({
|
|
738
|
+
severity: "error",
|
|
739
|
+
field: "round_contract",
|
|
740
|
+
check: CHECK_CONTRACT_REQUIRED,
|
|
741
|
+
detail: requirement.reason === "no_active_contract"
|
|
742
|
+
? "A verification command is configured and this round continues, but no " +
|
|
743
|
+
"Round Contract is active for the next round and none was proposed. " +
|
|
744
|
+
"Submit round_contract with a work_item and at least one item bound to a " +
|
|
745
|
+
"configured after-capable command."
|
|
746
|
+
: "The active Round Contract closed this round and this round continues, " +
|
|
747
|
+
"but no successor round_contract was proposed. Submit the next slice's " +
|
|
748
|
+
"contract (a new work_item with acceptance items).",
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/** v4.0.6: a successor for a closed contract must bring genuinely new
|
|
752
|
+
* acceptance conditions. Re-stating the same items would re-activate the old
|
|
753
|
+
* slice and re-verify it. Changing only work_item/scope is not a new slice. */
|
|
754
|
+
function checkContractSuccessorUnchanged(activeContract, statuses, selfEval) {
|
|
755
|
+
if (!activeContract)
|
|
756
|
+
return null;
|
|
757
|
+
if (statuses.closure === "open")
|
|
758
|
+
return null;
|
|
759
|
+
const proposal = selfEval.round_contract;
|
|
760
|
+
if (!proposal)
|
|
761
|
+
return null;
|
|
762
|
+
if (acceptanceItemsDiffer(proposal, activeContract))
|
|
763
|
+
return null;
|
|
764
|
+
return makeVerificationFlag({
|
|
765
|
+
severity: "error",
|
|
766
|
+
field: "round_contract",
|
|
767
|
+
check: CHECK_CONTRACT_SUCCESSOR_UNCHANGED,
|
|
768
|
+
detail: "The proposed successor repeats the closed contract's acceptance items " +
|
|
769
|
+
"verbatim. A successor must bring new acceptance conditions; changing only " +
|
|
770
|
+
"work_item or scope is not a new slice.",
|
|
985
771
|
});
|
|
986
772
|
}
|
|
987
773
|
// Main entry point
|
|
@@ -1058,54 +844,63 @@ function checkUserGateUnresolved(selfEval, gateEntries) {
|
|
|
1058
844
|
});
|
|
1059
845
|
}
|
|
1060
846
|
export function verifySelfEvaluation(selfEval, currentRound, vaultEntries, prevSelfEval = null, evidenceDeltas = [],
|
|
1061
|
-
/**
|
|
1062
|
-
*
|
|
1063
|
-
* was not properly restored before working. */
|
|
1064
|
-
backtrackSkippedFiles = [],
|
|
1065
|
-
/** M3 (v3.7.x): skipped-file git fingerprints at their failed rounds —
|
|
1066
|
-
* machine proof of "untouched since the rollback" for the restore check. */
|
|
1067
|
-
backtrackSkippedFingerprints = {},
|
|
1068
|
-
/** v2.12: Git HEAD commit of the backtrack restore point. When set, the
|
|
1069
|
-
* current git snapshot must sit at this commit — otherwise the workspace
|
|
1070
|
-
* was not restored and the round cannot be accepted. */
|
|
1071
|
-
backtrackTargetGitHead,
|
|
847
|
+
/** v3.7.1: gate records (task_type gate_opened / gate_decision) live
|
|
848
|
+
* outside the round prefix — the caller passes them in explicitly. */
|
|
1072
849
|
/** v3.7.1: gate records (task_type gate_opened / gate_decision) live
|
|
1073
850
|
* outside the round prefix — the caller passes them in explicitly. */
|
|
1074
851
|
gateEntries = [],
|
|
1075
852
|
/** v3.8.3: entrypoint files that no longer match the trusted round-start
|
|
1076
853
|
* baseline. Derived by the caller (it needs the in-flight trust map and
|
|
1077
854
|
* filesystem access); this function stays a pure function of its inputs. */
|
|
1078
|
-
entrypointDrift = [],
|
|
1079
|
-
/** v3.9: the
|
|
1080
|
-
|
|
855
|
+
entrypointDrift = [],
|
|
856
|
+
/** v3.9.4: whether the raw submission attempted
|
|
857
|
+
* `execution_report.files_changed` (from the persisted submittedFields
|
|
858
|
+
* manifest). Undefined falls back to "an execution report exists". */
|
|
859
|
+
filesReported,
|
|
860
|
+
/** v4.0.3: the ONE `RoundFacts` bundle the coordinator derived before
|
|
861
|
+
* verification. When supplied, the gate consumes it instead of deriving
|
|
862
|
+
* the active contract / item statuses a second time. The fallback exists
|
|
863
|
+
* for direct library callers and unit tests; it calls the same derivation
|
|
864
|
+
* function, so there is still exactly one rule. */
|
|
865
|
+
facts, maxRounds = Number.POSITIVE_INFINITY) {
|
|
1081
866
|
const flags = [];
|
|
1082
|
-
|
|
1083
|
-
// and other checks that need deeper history.
|
|
1084
|
-
const olderViolations = [];
|
|
1085
|
-
for (const entry of vaultEntries) {
|
|
1086
|
-
for (const v of entryViolations(entry))
|
|
1087
|
-
olderViolations.push(v);
|
|
1088
|
-
}
|
|
867
|
+
const filesReportedForGate = filesReported ?? (selfEval.execution_report != null);
|
|
1089
868
|
// v3.4: The ACTIVE contract this round executed under — derived from the
|
|
1090
869
|
// committed :feedback evals of earlier rounds. The submission's own
|
|
1091
870
|
// round_contract is a PROPOSAL for the next round and never participates
|
|
1092
871
|
// (the eval under verification is not yet committed, so it structurally
|
|
1093
872
|
// cannot influence the derivation).
|
|
1094
873
|
const committedRounds = derivationRounds(vaultEntries, currentRound);
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
874
|
+
const olderViolations = [];
|
|
875
|
+
for (const view of committedRounds) {
|
|
876
|
+
for (const v of view.constraintViolations ?? [])
|
|
877
|
+
olderViolations.push(v);
|
|
878
|
+
}
|
|
879
|
+
// v4.0.3: the coordinator computes this bundle once and passes it in. A
|
|
880
|
+
// direct caller gets the SAME derivation — never a second rule.
|
|
881
|
+
const roundFacts = facts ?? deriveRoundFacts({
|
|
1100
882
|
rounds: committedRounds,
|
|
1101
883
|
currentRound,
|
|
1102
|
-
|
|
1103
|
-
|
|
884
|
+
inFlight: {
|
|
885
|
+
currentReport: selfEval.execution_report ?? null,
|
|
886
|
+
currentDeltas: evidenceDeltas,
|
|
887
|
+
currentBlocked: declaresBlocked(selfEval),
|
|
888
|
+
// v3.9.9/v4.0.6: without this, debt/premature checks cannot see a
|
|
889
|
+
// superseded closure and would report the old contract as merely open.
|
|
890
|
+
currentRedirect: selfEval.round_contract?.supersedes ?? null,
|
|
891
|
+
},
|
|
1104
892
|
commands: getPolicy().evidence.commands ?? [],
|
|
1105
893
|
});
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
894
|
+
const activeContract = roundFacts.activeContract;
|
|
895
|
+
const contractStatuses = roundFacts.itemStatuses;
|
|
896
|
+
// v4.0.3: the ONLY machine-verified criterion ids are contract-bound
|
|
897
|
+
// criterion facts and verified active-contract items. Agent claims and
|
|
898
|
+
// self-reported tests never enter this set.
|
|
899
|
+
const machineVerified = machineVerifiedCriterionIds({
|
|
900
|
+
criterionFacts: roundFacts.criterionFacts,
|
|
901
|
+
activeContract,
|
|
902
|
+
itemStatuses: contractStatuses,
|
|
903
|
+
});
|
|
1109
904
|
// v3.2: Runtime machine-verification status (git observed / command passed /
|
|
1110
905
|
// absent). v3.6: feeds the merged R8 check (providerStatus lens) that
|
|
1111
906
|
// never self-skips.
|
|
@@ -1120,34 +915,30 @@ backtrackTargetDirty) {
|
|
|
1120
915
|
// v2.12/v3.6/v3.7: the single success-evidence semantic — empty/missing-
|
|
1121
916
|
// evidence arm (ex-R3) + claims arm (providerStatus lens, tamper-aware)
|
|
1122
917
|
() => checkSuccessWithoutVerifiedEvidence(selfEval, evidenceStatus),
|
|
1123
|
-
// v2.12:
|
|
1124
|
-
() => checkUnverifiedCriteriaClaims(selfEval,
|
|
1125
|
-
// Declared outcome vs the required core success flag.
|
|
1126
|
-
() => checkOutcomeConsistency(selfEval),
|
|
918
|
+
// v2.12/v4.0.3: per-claim machine verification
|
|
919
|
+
() => checkUnverifiedCriteriaClaims(selfEval, machineVerified),
|
|
1127
920
|
// v2.12: retroactive claims against prior rounds
|
|
1128
921
|
() => checkRetroactiveClaims(selfEval, vaultEntries, currentRound),
|
|
1129
922
|
() => checkDuplicateConstraintDiscovery(selfEval, prevSelfEval, olderViolations),
|
|
1130
|
-
() => checkRecurringViolation(selfEval, prevSelfEval,
|
|
923
|
+
() => checkRecurringViolation(selfEval, prevSelfEval, committedRounds, currentRound),
|
|
1131
924
|
() => checkRetractFreshConstraint(selfEval, prevSelfEval, currentRound),
|
|
1132
925
|
// v1.18: Cross-validate agent-reported files_changed against git evidence
|
|
1133
|
-
() => checkEvidenceIntegrity(selfEval, evidenceDeltas),
|
|
926
|
+
() => checkEvidenceIntegrity(selfEval, evidenceDeltas, filesReportedForGate),
|
|
1134
927
|
() => checkRequiredCommandEvidence(selfEval, evidenceDeltas),
|
|
1135
|
-
// v2.0: Cross-validate agent-reported tests_reported against command output
|
|
1136
|
-
() => checkCommandEvidenceIntegrity(selfEval, evidenceDeltas),
|
|
1137
928
|
// v3.3: Verification domain integrity — command entrypoint / test files
|
|
1138
929
|
// changed in the same round the command ran
|
|
1139
930
|
() => checkVerificationDomainIntegrity(evidenceDeltas, entrypointDrift),
|
|
1140
|
-
//
|
|
1141
|
-
|
|
1142
|
-
// v2.12: Post-backtrack git HEAD restore check
|
|
1143
|
-
() => checkBacktrackGitHeadRestore(evidenceDeltas, backtrackTargetGitHead, beforeEvidence, backtrackTargetDirty),
|
|
931
|
+
// v3.9.7: restore verification happens before the redo prompt, not on
|
|
932
|
+
// the redo submission.
|
|
1144
933
|
// v3.8: Round Contract checks over the derived item model. Declaration
|
|
1145
934
|
// quality is enforced structurally (contract_invalid) before the round
|
|
1146
935
|
// advances, so the gate only reports execution state: scope drift,
|
|
1147
936
|
// claimed-but-unverified items, and an ignored replacement proposal.
|
|
1148
937
|
() => checkRoundScopeDrift(activeContract, evidenceDeltas),
|
|
1149
|
-
() => checkContractItemsUnverified(contractStatuses),
|
|
938
|
+
() => checkContractItemsUnverified(contractStatuses, selfEval.execution_report ?? null),
|
|
1150
939
|
() => checkContractPremature(activeContract, contractStatuses, selfEval),
|
|
940
|
+
() => checkContractRequired(activeContract, contractStatuses, selfEval, deriveConfiguredCapability(getPolicy()).contractVerificationAvailable, currentRound < maxRounds),
|
|
941
|
+
() => checkContractSuccessorUnchanged(activeContract, contractStatuses, selfEval),
|
|
1151
942
|
// v3.7.1: opt-in gate-layer blocking (skipped when disabled)
|
|
1152
943
|
() => checkUserGateUnresolved(selfEval, gateEntries),
|
|
1153
944
|
];
|