@cassiomc1/forgeloop 1.3.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +20 -8
- package/EXECUTION_STATE.md +60 -0
- package/LOOP_ENGINEERING.md +135 -5
- package/LOOP_SYSTEM_DESIGN.md +54 -1
- package/PROTOCOL_INTEGRATION.md +87 -0
- package/QUALITY_SCORECARD.md +2 -0
- package/README.md +69 -9
- package/TERMINOLOGY.md +15 -0
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/THREAT_MODEL.md +59 -1
- package/docs/ARTIFACT_REFERENCE.md +183 -0
- package/docs/CLI_REFERENCE.md +391 -6
- package/docs/CROSS_HARNESS_CONTINUITY.md +23 -0
- package/docs/DIAGNOSTIC_MODEL.md +181 -0
- package/docs/DOCUMENTATION_GUIDE.md +36 -13
- package/docs/EXECUTION_TRACE.md +76 -0
- package/docs/GETTING_STARTED.md +1 -0
- package/docs/MCP.md +159 -0
- package/docs/RECIPES.md +149 -0
- package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
- package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
- package/docs/TROUBLESHOOTING.md +217 -3
- package/docs/UNIVERSAL_INTEGRATION.md +48 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +13797 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +37 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5002 -0
- package/docs/diagrams/README.md +55 -0
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +122 -0
- package/docs/diagrams/manifest.json +42 -0
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +20 -0
- package/package.json +21 -8
- package/schemas/action.schema.json +100 -0
- package/schemas/approval.schema.json +51 -0
- package/schemas/capability-policy.schema.json +41 -0
- package/schemas/diagnostic-case.schema.json +85 -0
- package/schemas/execution-receipt.schema.json +16 -0
- package/schemas/hypothesis-disposition.schema.json +16 -0
- package/schemas/intervention.schema.json +27 -0
- package/schemas/policy-lock.schema.json +1 -0
- package/schemas/policy-snapshot.schema.json +2 -0
- package/schemas/task-recovery.schema.json +61 -0
- package/schemas/trajectory-evaluation.schema.json +64 -0
- package/schemas/trajectory-scenario.schema.json +42 -0
- package/src/cli.js +267 -347
- package/src/commands/action-authorize.js +41 -0
- package/src/commands/action-propose.js +10 -0
- package/src/commands/action-reconcile.js +10 -0
- package/src/commands/action-record.js +47 -0
- package/src/commands/action-show.js +10 -0
- package/src/commands/action-verify.js +10 -0
- package/src/commands/advance.js +7 -2
- package/src/commands/approval-request.js +64 -0
- package/src/commands/approval-resolve.js +10 -0
- package/src/commands/audit.js +5 -0
- package/src/commands/baseline.js +3 -3
- package/src/commands/eval.js +6 -0
- package/src/commands/history.js +18 -0
- package/src/commands/init.js +2 -2
- package/src/commands/inspect.js +55 -0
- package/src/commands/metrics.js +7 -0
- package/src/commands/next.js +8 -2
- package/src/commands/policy-discover.js +2 -2
- package/src/commands/progress.js +6 -2
- package/src/commands/record-diagnosis.js +37 -1
- package/src/commands/record-hypothesis-disposition.js +45 -0
- package/src/commands/record-intervention.js +35 -0
- package/src/commands/reflect.js +38 -0
- package/src/commands/report.js +9 -1
- package/src/commands/run-action.js +18 -0
- package/src/commands/status.js +17 -0
- package/src/commands/task-create.js +39 -1
- package/src/commands/task-list.js +14 -1
- package/src/commands/task-lock-status.js +2 -2
- package/src/commands/task-recover.js +202 -0
- package/src/commands/task-repair-legacy-recovery.js +417 -0
- package/src/commands/task-resume.js +172 -0
- package/src/commands/task-scope.js +23 -4
- package/src/commands/task-show.js +18 -4
- package/src/commands/trace.js +34 -0
- package/src/commands/validate-protocol.js +40 -15
- package/src/core/action-authorization.js +106 -0
- package/src/core/action-constants.js +86 -0
- package/src/core/action-execution.js +105 -0
- package/src/core/action-ledger-projection.js +302 -0
- package/src/core/action-model.js +581 -0
- package/src/core/action-readiness.js +141 -0
- package/src/core/action-reconciliation-policy.js +49 -0
- package/src/core/action-reconciliation.js +66 -0
- package/src/core/action-verification.js +111 -0
- package/src/core/actions.js +462 -0
- package/src/core/approvals.js +405 -0
- package/src/core/artifact-registry.js +60 -0
- package/src/core/audit.js +45 -4
- package/src/core/bundles.js +30 -0
- package/src/core/capability-policy.js +226 -0
- package/src/core/cli-command-definitions.js +260 -5
- package/src/core/command-executors.js +543 -0
- package/src/core/command-input.js +107 -0
- package/src/core/command-runtime.js +117 -0
- package/src/core/completion-artifacts.js +39 -15
- package/src/core/completion-ownership.js +88 -0
- package/src/core/completion-recovery-rebind.js +194 -0
- package/src/core/completion.js +70 -0
- package/src/core/continuity-reconciliation.js +24 -5
- package/src/core/diagnostic-model.js +396 -0
- package/src/core/diagnostic-projection.js +51 -0
- package/src/core/diagnostic-record.js +360 -0
- package/src/core/error-codes.js +461 -1
- package/src/core/events.js +171 -2
- package/src/core/execution-prerequisites.js +4 -1
- package/src/core/execution.js +26 -188
- package/src/core/failure-signature.js +70 -0
- package/src/core/failure-surface.js +57 -0
- package/src/core/filesystem.js +55 -6
- package/src/core/history.js +110 -0
- package/src/core/hypothesis-projection.js +85 -0
- package/src/core/information-gain-projection.js +283 -0
- package/src/core/information-gain.js +138 -0
- package/src/core/inspect.js +132 -7
- package/src/core/integration-invocation-policy.js +217 -0
- package/src/core/integration-limits.js +20 -0
- package/src/core/integration-resources.js +178 -0
- package/src/core/next-action-model.js +94 -0
- package/src/core/next-action.js +490 -3
- package/src/core/phase.js +42 -22
- package/src/core/policy-engine.js +113 -6
- package/src/core/preflight-consistency.js +31 -5
- package/src/core/preflight.js +19 -2
- package/src/core/prepared-execution.js +227 -0
- package/src/core/progress.js +41 -4
- package/src/core/project-root.js +21 -0
- package/src/core/protocol-info.js +61 -0
- package/src/core/protocol.js +14 -0
- package/src/core/receipt.js +1 -0
- package/src/core/reconcile-closure.js +35 -10
- package/src/core/recovery-history.js +116 -0
- package/src/core/reflection.js +305 -0
- package/src/core/resumability.js +57 -3
- package/src/core/schema-validation.js +9 -0
- package/src/core/strategy-analysis.js +97 -0
- package/src/core/task-claim-state.js +272 -0
- package/src/core/task-command.js +5 -1
- package/src/core/task-conflict-inspection.js +321 -0
- package/src/core/task-context.js +32 -29
- package/src/core/task-discovery.js +14 -1
- package/src/core/task-lock.js +216 -22
- package/src/core/task-paths.js +31 -2
- package/src/core/task-recovery-migration.js +192 -0
- package/src/core/task-recovery.js +205 -0
- package/src/core/task-scope.js +33 -1
- package/src/core/task-snapshot.js +53 -0
- package/src/core/templates.js +9 -0
- package/src/core/trace.js +548 -0
- package/src/core/trajectory-evaluation.js +71 -0
- package/src/core/trajectory-metrics.js +80 -0
- package/src/core/transaction.js +36 -2
- package/src/core/work-state.js +10 -5
- package/src/integration.js +47 -0
- package/docs/assets/forgeloop-flow.svg +0 -1
- package/docs/forgeloop-flow.mmd +0 -51
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
|
+
import { compareFailureSurface } from "./failure-surface.js";
|
|
3
|
+
|
|
4
|
+
export function computeStrategyFingerprints(trace) {
|
|
5
|
+
const byCycle = new Map();
|
|
6
|
+
const add = (cycle, component, fingerprint) => {
|
|
7
|
+
if (!byCycle.has(cycle)) byCycle.set(cycle, { hypotheses: new Set(), contributors: new Set(), interventions: new Set() });
|
|
8
|
+
byCycle.get(cycle)[component].add(fingerprint);
|
|
9
|
+
};
|
|
10
|
+
for (const diagnosticCase of trace.diagnostics.cases) {
|
|
11
|
+
for (const hypothesis of diagnosticCase.hypotheses ?? []) {
|
|
12
|
+
add(diagnosticCase.verificationCycle, "hypotheses", `${hypothesis.statement}`.trim().toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
for (const contributor of diagnosticCase.contributors ?? []) {
|
|
15
|
+
add(diagnosticCase.verificationCycle, "contributors", `${contributor.statement}`.trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const legacy of trace.diagnostics.legacyDiagnoses) {
|
|
19
|
+
add(legacy.verificationCycle, "hypotheses", `${legacy.hypothesis}`.trim().toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
for (const intervention of trace.diagnostics.interventions) {
|
|
22
|
+
add(intervention.verificationCycle, "interventions", intervention.interventionSemanticFingerprint ?? `${intervention.intervention?.statement ?? ""}`.trim().toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [...byCycle.entries()]
|
|
26
|
+
.sort(([a], [b]) => a - b)
|
|
27
|
+
.map(([cycle, components]) => ({
|
|
28
|
+
verificationCycle: cycle,
|
|
29
|
+
strategyFingerprint: JSON.stringify([
|
|
30
|
+
[...components.hypotheses].sort(),
|
|
31
|
+
[...components.contributors].sort(),
|
|
32
|
+
[...components.interventions].sort(),
|
|
33
|
+
]),
|
|
34
|
+
components: {
|
|
35
|
+
hypotheses: [...components.hypotheses].sort(),
|
|
36
|
+
contributors: [...components.contributors].sort(),
|
|
37
|
+
interventions: [...components.interventions].sort(),
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function detectOscillation(strategies) {
|
|
43
|
+
const fingerprints = strategies.map((strategy) => strategy.strategyFingerprint);
|
|
44
|
+
const patterns = [];
|
|
45
|
+
for (let i = 2; i < fingerprints.length; i++) {
|
|
46
|
+
if (fingerprints[i] === fingerprints[i - 2] && fingerprints[i] !== fingerprints[i - 1]) {
|
|
47
|
+
patterns.push({ kind: "A_B_A", cycles: [strategies[i - 2].verificationCycle, strategies[i - 1].verificationCycle, strategies[i].verificationCycle] });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (let i = 3; i < fingerprints.length; i++) {
|
|
51
|
+
if (fingerprints[i] === fingerprints[i - 2]
|
|
52
|
+
&& fingerprints[i - 1] === fingerprints[i - 3]
|
|
53
|
+
&& fingerprints[i] !== fingerprints[i - 1]) {
|
|
54
|
+
patterns.push({ kind: "A_B_A_B", cycles: [strategies[i - 3].verificationCycle, strategies[i - 2].verificationCycle, strategies[i - 1].verificationCycle, strategies[i].verificationCycle] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const detected = patterns.length > 0;
|
|
58
|
+
return { detected, patterns };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function evaluateInterventionEffectiveness(trace, surfacesByCycle) {
|
|
62
|
+
const interventions = [];
|
|
63
|
+
const list = trace.diagnostics.interventions;
|
|
64
|
+
for (const entry of list) {
|
|
65
|
+
const cycle = entry.verificationCycle;
|
|
66
|
+
const laterVerificationCycles = Object.keys(surfacesByCycle).map(Number).filter((value) => value > cycle);
|
|
67
|
+
const nextCycle = laterVerificationCycles.length > 0 ? Math.min(...laterVerificationCycles) : null;
|
|
68
|
+
|
|
69
|
+
let effectiveness = "PENDING";
|
|
70
|
+
if (nextCycle !== null) {
|
|
71
|
+
const previousSurface = surfacesByCycle[cycle]?.surface ?? [];
|
|
72
|
+
const currentSurface = surfacesByCycle[nextCycle]?.surface ?? [];
|
|
73
|
+
const comparison = compareFailureSurface(previousSurface, currentSurface);
|
|
74
|
+
const signatureChanged = JSON.stringify(surfacesByCycle[cycle]?.signatures ?? []) !== JSON.stringify(surfacesByCycle[nextCycle]?.signatures ?? []);
|
|
75
|
+
if (comparison.direction === "REDUCED") effectiveness = "IMPROVED";
|
|
76
|
+
else if (comparison.direction === "EXPANDED") effectiveness = "REGRESSED";
|
|
77
|
+
else if (signatureChanged || comparison.changed) effectiveness = "INFORMATIVE";
|
|
78
|
+
else effectiveness = "NON_INFORMATIVE";
|
|
79
|
+
}
|
|
80
|
+
interventions.push({
|
|
81
|
+
id: entry.intervention?.id ?? null,
|
|
82
|
+
sequence: entry.sequence,
|
|
83
|
+
verificationCycle: cycle,
|
|
84
|
+
kind: entry.intervention?.kind ?? null,
|
|
85
|
+
reversible: entry.intervention?.reversible ?? null,
|
|
86
|
+
semanticFingerprint: entry.interventionSemanticFingerprint ?? null,
|
|
87
|
+
effectiveness,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return interventions;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
export function interventionSemanticFingerprintOf(entry) {
|
|
95
|
+
return entry?.interventionSemanticFingerprint
|
|
96
|
+
?? (entry?.intervention ? canonicalFingerprint(entry.intervention) : null);
|
|
97
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E_COMPLETION_OWNERSHIP_UNPROVEN,
|
|
3
|
+
E_TASK_CLAIM_OWNERSHIP_INCONSISTENT,
|
|
4
|
+
E_TASK_COMPLETE,
|
|
5
|
+
E_TASK_RECOVERED,
|
|
6
|
+
E_TASK_RECOVERY_INCONSISTENT,
|
|
7
|
+
} from "./error-codes.js";
|
|
8
|
+
import { validateCompletionOwnershipProof } from "./completion-ownership.js";
|
|
9
|
+
import { validateEventLedger } from "./events.js";
|
|
10
|
+
import { classifyRecoveryHistory } from "./recovery-history.js";
|
|
11
|
+
import { readTaskDescriptor } from "./task-descriptor.js";
|
|
12
|
+
import { normalizeWriteClaims } from "./task-scope.js";
|
|
13
|
+
import {
|
|
14
|
+
readTaskRecovery,
|
|
15
|
+
validateTaskRecoveryConsistency,
|
|
16
|
+
} from "./task-recovery.js";
|
|
17
|
+
import { readWorkState } from "./work-state.js";
|
|
18
|
+
|
|
19
|
+
function ownershipError(message, cause = null) {
|
|
20
|
+
return {
|
|
21
|
+
code: E_TASK_RECOVERY_INCONSISTENT,
|
|
22
|
+
message,
|
|
23
|
+
...(cause?.code ? { causeCode: cause.code } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function appendClaims(target, claims, errors, source) {
|
|
28
|
+
if (claims === undefined || claims === null) return;
|
|
29
|
+
try {
|
|
30
|
+
target.push(...normalizeWriteClaims(claims));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
errors.push(ownershipError(`Invalid ${source} write claims: ${error.message}`, error));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Collects every input needed to resolve claim ownership from one immutable
|
|
38
|
+
* snapshot: descriptor, work state, recovery tombstone, validated ledger, and
|
|
39
|
+
* classified recovery history. Classification is a pure function of this
|
|
40
|
+
* evidence, so conflict inspection can reuse it without rereading artifacts.
|
|
41
|
+
*/
|
|
42
|
+
export async function collectTaskClaimEvidence(target, {
|
|
43
|
+
taskId,
|
|
44
|
+
packageRoot,
|
|
45
|
+
descriptor: suppliedDescriptor = null,
|
|
46
|
+
state: suppliedState = null,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const errors = [];
|
|
49
|
+
let descriptor = suppliedDescriptor;
|
|
50
|
+
let state = suppliedState;
|
|
51
|
+
let recovery = null;
|
|
52
|
+
let ledger = { valid: false, events: [], errors: [] };
|
|
53
|
+
|
|
54
|
+
if (!descriptor) {
|
|
55
|
+
try {
|
|
56
|
+
descriptor = (await readTaskDescriptor(target, taskId, packageRoot)).value;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
errors.push(ownershipError(`Task descriptor cannot establish claim ownership: ${error.message}`, error));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!state) {
|
|
62
|
+
try {
|
|
63
|
+
state = await readWorkState(target, { taskId, packageRoot });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
errors.push(ownershipError(`Task work state cannot establish claim ownership: ${error.message}`, error));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
recovery = (await readTaskRecovery(target, { taskId, packageRoot }))?.value ?? null;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
errors.push(ownershipError(`Task recovery artifact cannot establish claim ownership: ${error.message}`, error));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
errors.push(ownershipError(`Task event ledger cannot establish claim ownership: ${error.message}`, error));
|
|
79
|
+
}
|
|
80
|
+
if (!ledger.valid) {
|
|
81
|
+
for (const error of ledger.errors) {
|
|
82
|
+
errors.push(ownershipError(`Task event ledger is invalid: ${error.message}`, error));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (ledger.events.some((event) => event.taskId !== taskId)) {
|
|
86
|
+
errors.push(ownershipError(`Task event ledger contains an event for a different task`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const history = classifyRecoveryHistory(ledger.events);
|
|
90
|
+
const descriptorClaims = [];
|
|
91
|
+
appendClaims(descriptorClaims, descriptor?.writeClaims, errors, "descriptor");
|
|
92
|
+
const normalizedDescriptorClaims = normalizeWriteClaims(descriptorClaims);
|
|
93
|
+
|
|
94
|
+
const historicalClaims = [...normalizedDescriptorClaims];
|
|
95
|
+
for (const cycle of history.recoveries) {
|
|
96
|
+
appendClaims(historicalClaims, cycle.event?.details?.releasedClaims, errors, "recovery ledger");
|
|
97
|
+
}
|
|
98
|
+
appendClaims(historicalClaims, recovery?.releasedClaims, errors, "recovery artifact");
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
taskId,
|
|
102
|
+
phase: state?.phase ?? null,
|
|
103
|
+
descriptor,
|
|
104
|
+
state,
|
|
105
|
+
recovery,
|
|
106
|
+
ledger,
|
|
107
|
+
history,
|
|
108
|
+
normalizedDescriptorClaims,
|
|
109
|
+
historicalWriteClaims: normalizeWriteClaims(historicalClaims),
|
|
110
|
+
errors,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function inconsistentResult({ taskId, phase, historicalWriteClaims, recovery, errors }) {
|
|
115
|
+
const reasonCodes = [
|
|
116
|
+
E_TASK_CLAIM_OWNERSHIP_INCONSISTENT,
|
|
117
|
+
...errors.flatMap((error) => [error.code, error.causeCode]).filter(Boolean),
|
|
118
|
+
];
|
|
119
|
+
return {
|
|
120
|
+
taskId,
|
|
121
|
+
phase,
|
|
122
|
+
historicalWriteClaims,
|
|
123
|
+
effectiveWriteClaims: historicalWriteClaims,
|
|
124
|
+
writeClaims: historicalWriteClaims,
|
|
125
|
+
claimState: "INCONSISTENT",
|
|
126
|
+
mutationAllowed: false,
|
|
127
|
+
recovery,
|
|
128
|
+
recoveryStatus: "INCONSISTENT",
|
|
129
|
+
valid: false,
|
|
130
|
+
ownershipValid: false,
|
|
131
|
+
reasonCodes: [...new Set(reasonCodes)],
|
|
132
|
+
errors,
|
|
133
|
+
ownershipErrors: errors,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Deterministic claim-ownership decision table over collected evidence.
|
|
139
|
+
* Claims are released only for validated canonical completion or validated
|
|
140
|
+
* active recovery; everything else fails closed.
|
|
141
|
+
*/
|
|
142
|
+
export function classifyTaskClaimState(evidence) {
|
|
143
|
+
const {
|
|
144
|
+
taskId,
|
|
145
|
+
phase,
|
|
146
|
+
recovery,
|
|
147
|
+
ledger,
|
|
148
|
+
history,
|
|
149
|
+
normalizedDescriptorClaims,
|
|
150
|
+
historicalWriteClaims,
|
|
151
|
+
} = evidence;
|
|
152
|
+
|
|
153
|
+
// Classification is side-effect free: never mutate the supplied evidence.
|
|
154
|
+
const errors = [...(evidence.errors ?? [])];
|
|
155
|
+
errors.push(...validateTaskRecoveryConsistency({
|
|
156
|
+
taskId,
|
|
157
|
+
recovery,
|
|
158
|
+
events: ledger.events,
|
|
159
|
+
historicalWriteClaims: normalizedDescriptorClaims,
|
|
160
|
+
recoveryHistory: history,
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
if (errors.length > 0) {
|
|
164
|
+
return inconsistentResult({ taskId, phase, historicalWriteClaims, recovery, errors });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (phase === "COMPLETE") {
|
|
168
|
+
const proof = validateCompletionOwnershipProof({ taskId, state: evidence.state, ledger });
|
|
169
|
+
if (!proof.valid) {
|
|
170
|
+
return inconsistentResult({
|
|
171
|
+
taskId,
|
|
172
|
+
phase,
|
|
173
|
+
historicalWriteClaims,
|
|
174
|
+
recovery,
|
|
175
|
+
errors: [
|
|
176
|
+
ownershipError(
|
|
177
|
+
"COMPLETE work-state lacks canonical lifecycle/ledger completion proof; claims stay reserved",
|
|
178
|
+
{ code: E_COMPLETION_OWNERSHIP_UNPROVEN },
|
|
179
|
+
),
|
|
180
|
+
...proof.errors.map((error) => ownershipError(error.message, { code: error.code })),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
taskId,
|
|
186
|
+
phase,
|
|
187
|
+
historicalWriteClaims,
|
|
188
|
+
effectiveWriteClaims: [],
|
|
189
|
+
writeClaims: [],
|
|
190
|
+
claimState: "RELEASED_BY_COMPLETION",
|
|
191
|
+
mutationAllowed: false,
|
|
192
|
+
recovery: null,
|
|
193
|
+
recoveryStatus: "NOT_APPLICABLE",
|
|
194
|
+
valid: true,
|
|
195
|
+
ownershipValid: true,
|
|
196
|
+
reasonCodes: [],
|
|
197
|
+
errors: [],
|
|
198
|
+
ownershipErrors: [],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (history.activeRecovery) {
|
|
203
|
+
// The consistency validator above guarantees that the active ledger cycle
|
|
204
|
+
// and recovery artifact identify the same recovery before this branch.
|
|
205
|
+
return {
|
|
206
|
+
taskId,
|
|
207
|
+
phase,
|
|
208
|
+
historicalWriteClaims,
|
|
209
|
+
effectiveWriteClaims: [],
|
|
210
|
+
writeClaims: [],
|
|
211
|
+
claimState: "RELEASED_BY_RECOVERY",
|
|
212
|
+
mutationAllowed: false,
|
|
213
|
+
recovery,
|
|
214
|
+
recoveryStatus: "ACTIVE",
|
|
215
|
+
valid: true,
|
|
216
|
+
ownershipValid: true,
|
|
217
|
+
reasonCodes: [],
|
|
218
|
+
errors: [],
|
|
219
|
+
ownershipErrors: [],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
taskId,
|
|
225
|
+
phase,
|
|
226
|
+
historicalWriteClaims,
|
|
227
|
+
effectiveWriteClaims: normalizedDescriptorClaims,
|
|
228
|
+
writeClaims: normalizedDescriptorClaims,
|
|
229
|
+
claimState: "ACTIVE",
|
|
230
|
+
mutationAllowed: true,
|
|
231
|
+
recovery: null,
|
|
232
|
+
recoveryStatus: history.completedRecoveries.length > 0 ? "COMPLETED" : "ABSENT",
|
|
233
|
+
valid: true,
|
|
234
|
+
ownershipValid: true,
|
|
235
|
+
reasonCodes: [],
|
|
236
|
+
errors: [],
|
|
237
|
+
ownershipErrors: [],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Resolve claim ownership from the task descriptor, work state, recovery
|
|
243
|
+
* tombstone, and the complete validated task ledger. Any disagreement retains
|
|
244
|
+
* every claim that can be recovered from validated inputs and fails closed.
|
|
245
|
+
*/
|
|
246
|
+
export async function resolveTaskClaimState(target, options = {}) {
|
|
247
|
+
const evidence = await collectTaskClaimEvidence(target, options);
|
|
248
|
+
return classifyTaskClaimState(evidence);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function assertTaskMutationAllowed(target, options = {}) {
|
|
252
|
+
const result = await resolveTaskClaimState(target, options);
|
|
253
|
+
if (result.mutationAllowed) return result;
|
|
254
|
+
|
|
255
|
+
const inconsistent = result.claimState === "INCONSISTENT";
|
|
256
|
+
const error = new Error(inconsistent
|
|
257
|
+
? `Task ${result.taskId} claim ownership is inconsistent; ordinary mutation is blocked`
|
|
258
|
+
: result.claimState === "RELEASED_BY_COMPLETION"
|
|
259
|
+
? `Task ${result.taskId} is COMPLETE and cannot be mutated`
|
|
260
|
+
: `Task ${result.taskId} is RECOVERED and its write claims are released; run task-resume before ordinary mutation`);
|
|
261
|
+
error.code = inconsistent
|
|
262
|
+
? E_TASK_CLAIM_OWNERSHIP_INCONSISTENT
|
|
263
|
+
: result.claimState === "RELEASED_BY_COMPLETION"
|
|
264
|
+
? E_TASK_COMPLETE
|
|
265
|
+
: E_TASK_RECOVERED;
|
|
266
|
+
error.taskId = result.taskId;
|
|
267
|
+
error.claimState = result.claimState;
|
|
268
|
+
error.reasonCodes = result.reasonCodes;
|
|
269
|
+
error.errors = result.errors;
|
|
270
|
+
error.recovery = result.recovery;
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
package/src/core/task-command.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { resolveTaskContext } from "./task-context.js";
|
|
1
|
+
import { resolveTaskContext, TASK_SELECTION_MODES } from "./task-context.js";
|
|
2
2
|
import { withTaskTransaction } from "./transaction.js";
|
|
3
|
+
import { assertTaskMutationAllowed } from "./task-claim-state.js";
|
|
3
4
|
|
|
4
5
|
export async function withResolvedTask(
|
|
5
6
|
target,
|
|
@@ -12,6 +13,7 @@ export async function withResolvedTask(
|
|
|
12
13
|
taskId: taskOption,
|
|
13
14
|
explicitRequired,
|
|
14
15
|
packageRoot: options.packageRoot,
|
|
16
|
+
selectionMode: TASK_SELECTION_MODES.READ,
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
return callback(taskContext);
|
|
@@ -29,10 +31,12 @@ export async function withTaskMutation(
|
|
|
29
31
|
taskId: taskOption,
|
|
30
32
|
explicitRequired,
|
|
31
33
|
packageRoot: options.packageRoot,
|
|
34
|
+
selectionMode: TASK_SELECTION_MODES.MUTATION,
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
if (taskContext) {
|
|
35
38
|
return withTaskTransaction({ target, taskId: taskContext.taskId, operation, packageRoot: options.packageRoot, recordCommitEvent: true }, async (transaction) => {
|
|
39
|
+
await assertTaskMutationAllowed(target, { taskId: taskContext.taskId, packageRoot: options.packageRoot });
|
|
36
40
|
return callback({ ...taskContext, transaction });
|
|
37
41
|
});
|
|
38
42
|
}
|